Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature]: support no std environments #1

Merged
merged 2 commits into from
Mar 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,29 @@ description = "Arbitrary precision decimal numbers"
documentation = "https://docs.rs/bigdecimal"
homepage = "https://github.com/akubera/bigdecimal-rs"
repository = "https://github.com/akubera/bigdecimal-rs"
keywords = ["mathematics", "numerics", "decimal", "arbitrary-precision", "floating-point", "no-std"]
keywords = [
"mathematics",
"numerics",
"decimal",
"arbitrary-precision",
"floating-point",
"no-std",
]
license = "MIT/Apache-2.0"

[dependencies]
libm = "0.2.6"
num-bigint = { version = "0.4", default-features = false }
num-integer = { version = "0.1", default-features = false }
num-traits = {version = "0.2", default-features = false }
num-traits = { version = "0.2", default-features = false }
serde = { version = "1.0", optional = true, default-features = false }

[dev-dependencies.serde_json]
version = "1.0"
[dev-dependencies]
serde_json = "1.0"
siphasher = { version = "0.3.10", default-features = false }

[features]
default = ["std"]
alloc = []
string-only = []
std = ["num-bigint/std", "num-integer/std", "num-traits/std", "serde/std"]
117 changes: 69 additions & 48 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
//!
//! println!("Input ({}) with 10 decimals: {} vs {})", input, dec, float);
//! ```
#![cfg_attr(not(feature = "std"), no_std)]
#![allow(clippy::unreadable_literal)]
#![allow(clippy::needless_return)]
#![allow(clippy::suspicious_arithmetic_impl)]
Expand All @@ -52,16 +53,24 @@ extern crate num_integer;
#[cfg(feature = "serde")]
extern crate serde;

use std::cmp::Ordering;
use std::convert::TryFrom;
use std::default::Default;
use std::error::Error;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::num::{ParseFloatError, ParseIntError};
use std::ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Rem, Sub, SubAssign};
use std::iter::Sum;
use std::str::{self, FromStr};
#[cfg(feature = "std")]
include!("./with_std.rs");

#[cfg(not(feature = "std"))]
include!("./without_std.rs");

#[cfg(all(not(feature = "std"), feature = "alloc"))]
include!("./with_alloc.rs");

use crate::cmp::Ordering;
use crate::convert::TryFrom;
use crate::default::Default;
use crate::hash::{Hash, Hasher};
use crate::num::{ParseFloatError, ParseIntError};
use crate::ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Rem, Sub, SubAssign};
use crate::iter::Sum;
use crate::str::FromStr;
use crate::string::{String, ToString};

use num_bigint::{BigInt, ParseBigIntError, Sign, ToBigInt};
use num_integer::Integer as IntegerTrait;
Expand Down Expand Up @@ -151,6 +160,17 @@ pub struct BigDecimal {
scale: i64,
}

#[cfg(not(feature = "std"))]
// f64::exp2 is only available in std, we have to use an external crate like libm
fn exp2(x: f64) -> f64 {
libm::exp2(x)
}

#[cfg(feature = "std")]
fn exp2(x: f64) -> f64 {
x.exp2()
}

impl BigDecimal {
/// Creates and initializes a `BigDecimal`.
///
Expand Down Expand Up @@ -399,7 +419,8 @@ impl BigDecimal {
let guess = {
let magic_guess_scale = 1.1951678538495576_f64;
let initial_guess = (self.int_val.bits() as f64 - self.scale as f64 * LOG2_10) / 2.0;
let res = magic_guess_scale * initial_guess.exp2();
let res = magic_guess_scale * exp2(initial_guess);

if res.is_normal() {
BigDecimal::try_from(res).unwrap()
} else {
Expand Down Expand Up @@ -471,7 +492,8 @@ impl BigDecimal {
let guess = {
let magic_guess_scale = 1.124960491619939_f64;
let initial_guess = (self.int_val.bits() as f64 - self.scale as f64 * LOG2_10) / 3.0;
let res = magic_guess_scale * initial_guess.exp2();
let res = magic_guess_scale * exp2(initial_guess);

if res.is_normal() {
BigDecimal::try_from(res).unwrap()
} else {
Expand Down Expand Up @@ -538,7 +560,7 @@ impl BigDecimal {

let magic_factor = 0.721507597259061_f64;
let initial_guess = scale * LOG2_10 - bits;
let res = magic_factor * initial_guess.exp2();
let res = magic_factor * exp2(initial_guess);

if res.is_normal() {
BigDecimal::try_from(res).unwrap()
Expand Down Expand Up @@ -689,7 +711,8 @@ impl fmt::Display for ParseBigDecimalError {
}
}

impl Error for ParseBigDecimalError {
#[cfg(feature = "std")]
impl std::error::Error for ParseBigDecimalError {
fn description(&self) -> &str {
"failed to parse bigint/biguint"
}
Expand Down Expand Up @@ -1012,7 +1035,7 @@ impl Sub<BigDecimal> for BigDecimal {
#[inline]
fn sub(self, rhs: BigDecimal) -> BigDecimal {
let mut lhs = self;
let scale = std::cmp::max(lhs.scale, rhs.scale);
let scale = cmp::max(lhs.scale, rhs.scale);

match lhs.scale.cmp(&rhs.scale) {
Ordering::Equal => {
Expand All @@ -1031,7 +1054,7 @@ impl<'a> Sub<&'a BigDecimal> for BigDecimal {
#[inline]
fn sub(self, rhs: &BigDecimal) -> BigDecimal {
let mut lhs = self;
let scale = std::cmp::max(lhs.scale, rhs.scale);
let scale = cmp::max(lhs.scale, rhs.scale);

match lhs.scale.cmp(&rhs.scale) {
Ordering::Equal => {
Expand Down Expand Up @@ -1407,7 +1430,7 @@ impl Rem<BigDecimal> for BigDecimal {

#[inline]
fn rem(self, other: BigDecimal) -> BigDecimal {
let scale = std::cmp::max(self.scale, other.scale);
let scale = cmp::max(self.scale, other.scale);

let num = self.take_and_scale(scale).int_val;
let den = other.take_and_scale(scale).int_val;
Expand All @@ -1421,7 +1444,7 @@ impl<'a> Rem<&'a BigDecimal> for BigDecimal {

#[inline]
fn rem(self, other: &BigDecimal) -> BigDecimal {
let scale = std::cmp::max(self.scale, other.scale);
let scale = cmp::max(self.scale, other.scale);
let num = self.take_and_scale(scale).int_val;
let den = &other.int_val;

Expand All @@ -1438,7 +1461,7 @@ impl<'a> Rem<BigDecimal> for &'a BigDecimal {

#[inline]
fn rem(self, other: BigDecimal) -> BigDecimal {
let scale = std::cmp::max(self.scale, other.scale);
let scale = cmp::max(self.scale, other.scale);
let num = &self.int_val;
let den = other.take_and_scale(scale).int_val;

Expand All @@ -1458,7 +1481,7 @@ impl<'a, 'b> Rem<&'b BigDecimal> for &'a BigDecimal {

#[inline]
fn rem(self, other: &BigDecimal) -> BigDecimal {
let scale = std::cmp::max(self.scale, other.scale);
let scale = cmp::max(self.scale, other.scale);
let num = &self.int_val;
let den = &other.int_val;

Expand Down Expand Up @@ -1765,7 +1788,7 @@ impl TryFrom<f32> for BigDecimal {

#[inline]
fn try_from(n: f32) -> Result<Self, Self::Error> {
BigDecimal::from_str(&format!("{:.PRECISION$e}", n, PRECISION = ::std::f32::DIGITS as usize))
BigDecimal::from_str(&format!("{:.PRECISION$e}", n, PRECISION = f32::DIGITS as usize))
}
}

Expand All @@ -1774,7 +1797,7 @@ impl TryFrom<f64> for BigDecimal {

#[inline]
fn try_from(n: f64) -> Result<Self, Self::Error> {
BigDecimal::from_str(&format!("{:.PRECISION$e}", n, PRECISION = ::std::f64::DIGITS as usize))
BigDecimal::from_str(&format!("{:.PRECISION$e}", n, PRECISION = f64::DIGITS as usize))
}
}

Expand Down Expand Up @@ -1809,11 +1832,11 @@ impl ToBigInt for BigDecimal {
/// Tools to help serializing/deserializing `BigDecimal`s
#[cfg(feature = "serde")]
mod bigdecimal_serde {
use crate::{fmt, TryFrom, FromStr};

use super::BigDecimal;
use serde::{de, ser};
use std::convert::TryFrom;
use std::fmt;
use std::str::FromStr;

#[allow(unused_imports)]
use num_traits::FromPrimitive;

Expand Down Expand Up @@ -1964,9 +1987,9 @@ mod bigdecimal_serde {
0.001,
12.34,
5.0 * 0.03125,
::std::f64::consts::PI,
::std::f64::consts::PI * 10000.0,
::std::f64::consts::PI * 30000.0,
crate::f64::consts::PI,
crate::f64::consts::PI * 10000.0,
crate::f64::consts::PI * 30000.0,
];
for n in vals {
let expected = BigDecimal::from_f64(n).unwrap();
Expand All @@ -1979,12 +2002,13 @@ mod bigdecimal_serde {
#[rustfmt::skip]
#[cfg(test)]
mod bigdecimal_tests {
use BigDecimal;
use crate::{BigDecimal, FromStr, TryFrom};
use num_traits::{ToPrimitive, FromPrimitive, Signed, Zero, One};
use std::convert::TryFrom;
use std::str::FromStr;
use num_bigint;

#[cfg(all(not(feature = "std"), feature = "alloc"))]
use crate::{vec, format, ToString};

#[test]
fn test_sum() {
let vals = vec![
Expand Down Expand Up @@ -2056,8 +2080,8 @@ mod bigdecimal_tests {
("12", 12),
("-13", -13),
("111", 111),
("-128", ::std::i8::MIN),
("127", ::std::i8::MAX),
("-128", i8::MIN),
("127", i8::MAX),
];
for (s, n) in vals {
let expected = BigDecimal::from_str(s).unwrap();
Expand All @@ -2077,10 +2101,10 @@ mod bigdecimal_tests {
("0.001", 0.001),
("12.34", 12.34),
("0.15625", 5.0 * 0.03125),
("3.141593", ::std::f32::consts::PI),
("31415.93", ::std::f32::consts::PI * 10000.0),
("94247.78", ::std::f32::consts::PI * 30000.0),
// ("3.14159265358979323846264338327950288f32", ::std::f32::consts::PI),
("3.141593", crate::f32::consts::PI),
("31415.93", crate::f32::consts::PI * 10000.0),
("94247.78", crate::f32::consts::PI * 30000.0),
// ("3.14159265358979323846264338327950288f32", crate::f32::consts::PI),

];
for (s, n) in vals {
Expand All @@ -2104,9 +2128,9 @@ mod bigdecimal_tests {
// ("12.3399999999999999", 12.34), // <- Precision 16 decimal points
("0.15625", 5.0 * 0.03125),
("0.3333333333333333", 1.0 / 3.0),
("3.141592653589793", ::std::f64::consts::PI),
("31415.92653589793", ::std::f64::consts::PI * 10000.0f64),
("94247.77960769380", ::std::f64::consts::PI * 30000.0f64),
("3.141592653589793", crate::f64::consts::PI),
("31415.92653589793", crate::f64::consts::PI * 10000.0f64),
("94247.77960769380", crate::f64::consts::PI * 30000.0f64),
];
for (s, n) in vals {
let expected = BigDecimal::from_str(s).unwrap();
Expand All @@ -2118,8 +2142,8 @@ mod bigdecimal_tests {

#[test]
fn test_nan_float() {
assert!(BigDecimal::try_from(std::f32::NAN).is_err());
assert!(BigDecimal::try_from(std::f64::NAN).is_err());
assert!(BigDecimal::try_from(f32::NAN).is_err());
assert!(BigDecimal::try_from(f64::NAN).is_err());
}

#[test]
Expand Down Expand Up @@ -2349,8 +2373,7 @@ mod bigdecimal_tests {

#[test]
fn test_hash_equal() {
use std::hash::{Hash, Hasher};
use std::collections::hash_map::DefaultHasher;
use crate::{Hash, Hasher, DefaultHasher};

fn hash<T>(obj: &T) -> u64
where T: Hash
Expand Down Expand Up @@ -2386,8 +2409,7 @@ mod bigdecimal_tests {

#[test]
fn test_hash_not_equal() {
use std::hash::{Hash, Hasher};
use std::collections::hash_map::DefaultHasher;
use crate::{Hash, Hasher, DefaultHasher};

fn hash<T>(obj: &T) -> u64
where T: Hash
Expand All @@ -2413,8 +2435,7 @@ mod bigdecimal_tests {

#[test]
fn test_hash_equal_scale() {
use std::hash::{Hash, Hasher};
use std::collections::hash_map::DefaultHasher;
use crate::{Hash, Hasher, DefaultHasher};

fn hash<T>(obj: &T) -> u64
where T: Hash
Expand Down
3 changes: 3 additions & 0 deletions src/with_alloc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
extern crate alloc;

use alloc::{format, string, vec};
4 changes: 4 additions & 0 deletions src/with_std.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
use std::{cmp, convert, default, fmt, hash, num, ops, iter, str, string, i8, f32, f64};

#[cfg(test)]
use std::collections::hash_map::DefaultHasher;
11 changes: 11 additions & 0 deletions src/without_std.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use core::{cmp, convert, default, fmt, hash, num, ops, iter, str, i8, f32, f64};

#[cfg(test)]
extern crate siphasher;

#[cfg(test)]
use siphasher::sip::SipHasher as DefaultHasher;

// Without this import we get the following error:
// error[E0599]: no method named `powi` found for type `f64` in the current scope
use num_traits::float::FloatCore;