Skip to content

Commit

Permalink
Merge pull request #97 from bigherc18/feat/support_no_std
Browse files Browse the repository at this point in the history
[Feature]: support `no_std` environments
  • Loading branch information
akubera committed May 10, 2023
2 parents 4112e15 + 1f63c88 commit b837f73
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 55 deletions.
26 changes: 19 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +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"]
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"]
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 @@ -53,16 +54,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 @@ -154,6 +163,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 @@ -407,7 +427,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 @@ -479,7 +500,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 @@ -546,7 +568,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 @@ -697,7 +719,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 @@ -1013,7 +1036,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 @@ -1032,7 +1055,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 @@ -1530,7 +1553,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 @@ -1544,7 +1567,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 @@ -1561,7 +1584,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 @@ -1581,7 +1604,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 @@ -1888,7 +1911,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 @@ -1897,7 +1920,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 @@ -1932,11 +1955,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 @@ -2087,9 +2110,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 @@ -2102,12 +2125,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 @@ -2179,8 +2203,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 @@ -2200,10 +2224,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 @@ -2227,9 +2251,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 @@ -2241,8 +2265,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 @@ -2508,8 +2532,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 @@ -2545,8 +2568,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 @@ -2572,8 +2594,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;

0 comments on commit b837f73

Please sign in to comment.