diff --git a/Cargo.toml b/Cargo.toml index e17ad59..15d41cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,22 +1,34 @@ [package] name = "bigdecimal" -version = "0.3.0" +version = "0.4.0" authors = ["Andrew Kubera"] 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"] +keywords = [ + "mathematics", + "numerics", + "decimal", + "arbitrary-precision", + "floating-point", + "no-std", +] license = "MIT/Apache-2.0" [dependencies] -num-bigint = "0.4" -num-integer = "0.1" -num-traits = "0.2" -serde = { version = "1.0", optional = true } +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 } +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"] diff --git a/src/lib.rs b/src/lib.rs index 8eb3dd6..316c107 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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)] @@ -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; @@ -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`. /// @@ -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 { @@ -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 { @@ -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() @@ -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" } @@ -1012,7 +1035,7 @@ impl Sub 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 => { @@ -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 => { @@ -1407,7 +1430,7 @@ impl Rem 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; @@ -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; @@ -1438,7 +1461,7 @@ impl<'a> Rem 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; @@ -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; @@ -1765,7 +1788,7 @@ impl TryFrom for BigDecimal { #[inline] fn try_from(n: f32) -> Result { - 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)) } } @@ -1774,7 +1797,7 @@ impl TryFrom for BigDecimal { #[inline] fn try_from(n: f64) -> Result { - 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)) } } @@ -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; @@ -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(); @@ -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![ @@ -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(); @@ -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 { @@ -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(); @@ -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] @@ -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(obj: &T) -> u64 where T: Hash @@ -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(obj: &T) -> u64 where T: Hash @@ -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(obj: &T) -> u64 where T: Hash diff --git a/src/with_alloc.rs b/src/with_alloc.rs new file mode 100644 index 0000000..511d71c --- /dev/null +++ b/src/with_alloc.rs @@ -0,0 +1,3 @@ +extern crate alloc; + +use alloc::{format, string, vec}; diff --git a/src/with_std.rs b/src/with_std.rs new file mode 100644 index 0000000..443c644 --- /dev/null +++ b/src/with_std.rs @@ -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; diff --git a/src/without_std.rs b/src/without_std.rs new file mode 100644 index 0000000..c12dc9f --- /dev/null +++ b/src/without_std.rs @@ -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;