From 113d5e59713fc4267b99d773bf0d6b62e9f8094e Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Thu, 13 Jul 2023 00:01:20 -0400 Subject: [PATCH 01/77] Begin v0.4.2 development --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 25c2935..d395b27 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bigdecimal" -version = "0.4.1" +version = "0.4.2+dev" authors = ["Andrew Kubera"] description = "Arbitrary precision decimal numbers" documentation = "https://docs.rs/bigdecimal" From 219df989b433de72675ba419cfc7587173047b4c Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Tue, 25 Jul 2023 00:09:07 -0400 Subject: [PATCH 02/77] Add Vec to stdlib --- src/with_std.rs | 2 ++ src/without_std.rs | 1 + 2 files changed, 3 insertions(+) diff --git a/src/with_std.rs b/src/with_std.rs index 4cddf7c..4ae185b 100644 --- a/src/with_std.rs +++ b/src/with_std.rs @@ -23,4 +23,6 @@ mod stdlib { #[cfg(test)] pub use std::collections::hash_map::DefaultHasher; + + pub use std::vec::Vec; } diff --git a/src/without_std.rs b/src/without_std.rs index 8e35874..4deb9bf 100644 --- a/src/without_std.rs +++ b/src/without_std.rs @@ -34,4 +34,5 @@ mod stdlib { pub use siphasher::sip::SipHasher as DefaultHasher; pub use alloc::string; + pub use alloc::vec::Vec; } From 94e2145d1bead3d7266b1f4335ddbe4bdf9b748e Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Tue, 25 Jul 2023 00:56:41 -0400 Subject: [PATCH 03/77] Reimplement BigDecimal::eq --- src/lib.rs | 46 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a77801b..a1c23dc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -68,7 +68,7 @@ use self::stdlib::default::Default; use self::stdlib::hash::{Hash, Hasher}; use self::stdlib::num::{ParseFloatError, ParseIntError}; use self::stdlib::ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Rem, Sub, SubAssign}; -use self::stdlib::iter::Sum; +use self::stdlib::iter::{self, Sum}; use self::stdlib::str::FromStr; use self::stdlib::string::{String, ToString}; use self::stdlib::fmt; @@ -1075,18 +1075,50 @@ impl Ord for BigDecimal { impl PartialEq for BigDecimal { #[inline] fn eq(&self, rhs: &BigDecimal) -> bool { - // fix scale and test equality + match (self.sign(), rhs.sign()) { + // both zero + (Sign::NoSign, Sign::NoSign) => return true, + // signs are different + (a, b) if a != b => return false, + // signs are same, do nothing + _ => {} + } + + let unscaled_int; + let scaled_int; + let trailing_zero_count; match self.scale.cmp(&rhs.scale) { Ordering::Greater => { - let scaled_int_val = &rhs.int_val * ten_to_the((self.scale - rhs.scale) as u64); - self.int_val == scaled_int_val + unscaled_int = &self.int_val; + scaled_int = &rhs.int_val; + trailing_zero_count = (self.scale - rhs.scale) as usize; } Ordering::Less => { - let scaled_int_val = &self.int_val * ten_to_the((rhs.scale - self.scale) as u64); - scaled_int_val == rhs.int_val + unscaled_int = &rhs.int_val; + scaled_int = &self.int_val; + trailing_zero_count = (rhs.scale - self.scale) as usize; } - Ordering::Equal => self.int_val == rhs.int_val, + Ordering::Equal => return self.int_val == rhs.int_val, } + + if trailing_zero_count < 20 { + let scaled_int = scaled_int * ten_to_the(trailing_zero_count as u64); + return &scaled_int == unscaled_int; + } + + let (_, unscaled_digits) = unscaled_int.to_radix_le(10); + let (_, scaled_digits) = scaled_int.to_radix_le(10); + + // different lengths with trailing zeros + if unscaled_digits.len() != scaled_digits.len() + trailing_zero_count { + return false; + } + + // add leading zero digits to digits that need scaled + let scaled = iter::repeat(&0u8).take(trailing_zero_count).chain(scaled_digits.iter()); + + // return true if all digits are the same + unscaled_digits.iter().zip(scaled).all(|(digit_a, digit_b)| { digit_a == digit_b }) } } From cce036106b3d7cd9499f4abd9bb308b2c540397a Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Fri, 28 Jul 2023 14:23:56 -0400 Subject: [PATCH 04/77] Reimplement ten_to_the --- src/lib.rs | 47 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a1c23dc..e058436 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -102,27 +102,50 @@ mod parsing; pub mod rounding; pub use rounding::RoundingMode; -#[inline(always)] +/// Return 10^pow +/// +/// Try to calculate this with fewest number of allocations +/// fn ten_to_the(pow: u64) -> BigInt { if pow < 20 { - BigInt::from(10u64.pow(pow as u32)) - } else { - let (half, rem) = pow.div_rem(&16); + return BigInt::from(10u64.pow(pow as u32)); + } - let mut x = ten_to_the(half); + // linear case of 10^pow = 10^(19 * count + rem) + if pow < 590 { + let ten_to_nineteen = 10u64.pow(19); - for _ in 0..4 { - x = &x * &x; - } + // count factors of 19 + let (count, rem) = pow.div_rem(&19); - if rem == 0 { - x - } else { - x * ten_to_the(rem) + let mut res = BigInt::from(ten_to_nineteen); + for _ in 1..count { + res *= ten_to_nineteen; + } + if rem != 0 { + res *= 10u64.pow(rem as u32); } + + return res; + } + + // use recursive algorithm where linear case might be too slow + let (quotient, rem) = pow.div_rem(&16); + let x = ten_to_the(quotient); + + let x2 = &x * &x; + let x4 = &x2 * &x2; + let x8 = &x4 * &x4; + let res = &x8 * &x8; + + if rem == 0 { + res + } else { + res * 10u64.pow(rem as u32) } } + #[inline(always)] fn count_decimal_digits(int: &BigInt) -> u64 { if int.is_zero() { From 3c03767bf6b9e876fce6fe150d42e52d8104ddde Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Tue, 15 Aug 2023 20:15:54 -0400 Subject: [PATCH 05/77] Hide artihmetic bench config behind toml comments --- Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d395b27..85556b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,6 @@ default = ["std"] string-only = [] std = ["num-bigint/std", "num-integer/std", "num-traits/std"] -[[bench]] -name = "arithmetic" -harness = false +# BENCH: [[bench]] +# BENCH: name = "arithmetic" +# BENCH: harness = false From afe28bb585b73816ceb43144882c16989381ef58 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Tue, 15 Aug 2023 20:37:12 -0400 Subject: [PATCH 06/77] Use --all-targets flag in ci cargo compilation commands --- .circleci/config.yml | 4 ++-- .gitlab-ci.yml | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d740dbb..bf4eb2b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -14,7 +14,7 @@ jobs: default: "buster" rust-features: type: string - default: "" + default: "--all-targets" docker: - image: rust:<< parameters.rust-version >>-<< parameters.debian-version >> environment: @@ -31,7 +31,7 @@ jobs: - bigdecimal-cargo- - run: name: Check - command: cargo check + command: cargo check << parameters.rust-features >> - save_cache: paths: - /usr/local/cargo diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f938a8a..2360805 100755 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -28,13 +28,13 @@ before_script: - rustc --version && cargo --version script: - - cargo check --tests + - cargo check --all-targets .script-cargo-build: &script-cargo-build before_script: - rustc --version && cargo --version script: - - cargo build --tests + - cargo build --all-targets .script-cargo-test: &script-cargo-test before_script: @@ -118,7 +118,7 @@ cargo:check: <<: *script-cargo-check script: # enable property tests for the stable 'pipeline' - - scripts/bigdecimal-property-tests cargo check --tests + - scripts/bigdecimal-property-tests cargo check --all-targets cargo:clippy: stage: check @@ -158,7 +158,7 @@ cargo:build-stable: <<: *script-cargo-build script: # enable property tests for the stable 'pipeline' - - scripts/bigdecimal-property-tests cargo build --tests + - scripts/bigdecimal-property-tests cargo build --all-targets cargo:test-stable: From 1a5b6c0e3bcb0af138a850ffb3543af0180adc79 Mon Sep 17 00:00:00 2001 From: Lucas Kent Date: Mon, 21 Aug 2023 03:07:36 +1000 Subject: [PATCH 07/77] Fix use of rerun-if-changed and clean build.rs --- build.rs | 50 ++++++++++++-------------------------------------- 1 file changed, 12 insertions(+), 38 deletions(-) diff --git a/build.rs b/build.rs index 7b35ce4..f60cfe1 100644 --- a/build.rs +++ b/build.rs @@ -1,50 +1,24 @@ #![allow(clippy::style)] -extern crate autocfg; - use std::env; use std::path::PathBuf; - -fn main() -> std::io::Result<()> { +fn main() { let ac = autocfg::new(); ac.emit_rustc_version(1, 70); - let outdir = match std::env::var_os("OUT_DIR") { - None => return Ok(()), - Some(outdir) => outdir, - }; - let outdir_path = PathBuf::from(outdir); - - write_default_precision(&outdir_path, "default_precision.rs")?; - Ok(()) -} - -/// Create default_precision.rs, containg definition of constant DEFAULT_PRECISION -fn write_default_precision(outdir_path: &PathBuf, filename: &str) -> std::io::Result<()> -{ - - let default_prec = env::var("RUST_BIGDECIMAL_DEFAULT_PRECISION") - .map(|s| s.parse::().expect("$RUST_BIGDECIMAL_DEFAULT_PRECISION must be an integer > 0")) - .map(|nz_num| nz_num.into()) - .unwrap_or(100u32); - - let default_precision_rs_path = outdir_path.join(filename); + let env_var = env::var("RUST_BIGDECIMAL_DEFAULT_PRECISION").unwrap_or_else(|_| "100".to_owned()); + println!("cargo:rerun-if-env-changed=RUST_BIGDECIMAL_DEFAULT_PRECISION"); - let default_precision = format!("const DEFAULT_PRECISION: u64 = {};", default_prec); + let outdir = std::env::var_os("OUT_DIR").unwrap(); + let rust_file_path = PathBuf::from(outdir).join("default_precision.rs"); - // Rewriting the file if it already exists with the same contents - // would force a rebuild. - match std::fs::read_to_string(&default_precision_rs_path) { - Ok(existing_contents) if existing_contents == default_precision => {}, - _ => { - std::fs::write(&default_precision_rs_path, default_precision) - .expect("Could not write big decimal default-precision file"); - } - }; + let default_prec: u32 = env_var + .parse::() + .expect("$RUST_BIGDECIMAL_DEFAULT_PRECISION must be an integer > 0") + .into(); - println!("cargo:rerun-if-changed={}", default_precision_rs_path.display()); - println!("cargo:rerun-if-env-changed={}", "RUST_BIGDECIMAL_DEFAULT_PRECISION"); + let rust_file_contents = format!("const DEFAULT_PRECISION: u64 = {};", default_prec); - Ok(()) -} + std::fs::write(rust_file_path, rust_file_contents).unwrap(); +} \ No newline at end of file From 4d4872fbe5c005247ce5597f7b81091679fff57b Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sun, 20 Aug 2023 13:16:03 -0400 Subject: [PATCH 08/77] Move write_default_precision_file back to separate function --- build.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/build.rs b/build.rs index f60cfe1..196093d 100644 --- a/build.rs +++ b/build.rs @@ -7,11 +7,17 @@ fn main() { let ac = autocfg::new(); ac.emit_rustc_version(1, 70); + let outdir: PathBuf = std::env::var_os("OUT_DIR").unwrap().into(); + write_default_precision_file(&outdir); +} + + +/// Create default_precision.rs, containg definition of constant DEFAULT_PRECISION loaded in src/lib.rs +fn write_default_precision_file(outdir: &PathBuf) { let env_var = env::var("RUST_BIGDECIMAL_DEFAULT_PRECISION").unwrap_or_else(|_| "100".to_owned()); println!("cargo:rerun-if-env-changed=RUST_BIGDECIMAL_DEFAULT_PRECISION"); - let outdir = std::env::var_os("OUT_DIR").unwrap(); - let rust_file_path = PathBuf::from(outdir).join("default_precision.rs"); + let rust_file_path = outdir.join("default_precision.rs"); let default_prec: u32 = env_var .parse::() @@ -21,4 +27,4 @@ fn main() { let rust_file_contents = format!("const DEFAULT_PRECISION: u64 = {};", default_prec); std::fs::write(rust_file_path, rust_file_contents).unwrap(); -} \ No newline at end of file +} From d1bfee282865a05b9203249539bc467d4a69fb0c Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Tue, 15 Aug 2023 23:31:13 -0400 Subject: [PATCH 09/77] Replace custom LOG2_10 with value from f64::consts --- src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e058436..8277e35 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -77,8 +77,7 @@ use num_bigint::{BigInt, ParseBigIntError, Sign}; use num_integer::Integer as IntegerTrait; pub use num_traits::{FromPrimitive, Num, One, Signed, ToPrimitive, Zero}; -#[allow(clippy::approx_constant)] // requires Rust 1.43.0 -const LOG2_10: f64 = 3.321928094887362_f64; +use stdlib::f64::consts::LOG2_10; // const DEFAULT_PRECISION: u64 = ${RUST_BIGDECIMAL_DEFAULT_PRECISION} or 100; From 9520e969024be6349f0d6bdbed614ac3e40fb809 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Thu, 14 Sep 2023 23:07:51 -0400 Subject: [PATCH 10/77] Add BigDecimal::with_precision_round method --- src/lib.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 8277e35..83fef18 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -426,6 +426,21 @@ impl BigDecimal { } } + /// Return this BigDecimal with the given precision, rounding if needed + pub fn with_precision_round(&self, prec: stdlib::num::NonZeroU64, round: RoundingMode) -> BigDecimal { + let digit_count = self.digits(); + let new_prec = prec.get().to_i64(); + let new_scale = new_prec + .zip(digit_count.to_i64()) + .map(|(new_prec, old_prec)| new_prec.checked_sub(old_prec)) + .flatten() + .map(|prec_diff| self.scale.checked_add(prec_diff)) + .flatten() + .expect("precision overflow"); + + self.with_scale_round(new_scale, round) + } + /// Return the sign of the `BigDecimal` as `num::bigint::Sign`. /// /// ``` From e231abc512df4f9aed60c5adb0c341e00ce45ac3 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Fri, 15 Sep 2023 00:55:33 -0400 Subject: [PATCH 11/77] Add BigDecimalRef struct and impl --- src/lib.rs | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 76 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 83fef18..5087476 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -73,7 +73,7 @@ use self::stdlib::str::FromStr; use self::stdlib::string::{String, ToString}; use self::stdlib::fmt; -use num_bigint::{BigInt, ParseBigIntError, Sign}; +use num_bigint::{BigInt, BigUint, ParseBigIntError, Sign}; use num_integer::Integer as IntegerTrait; pub use num_traits::{FromPrimitive, Num, One, Signed, ToPrimitive, Zero}; @@ -145,12 +145,16 @@ fn ten_to_the(pow: u64) -> BigInt { } -#[inline(always)] +/// Return number of decimal digits in integer fn count_decimal_digits(int: &BigInt) -> u64 { - if int.is_zero() { + count_decimal_digits_uint(int.magnitude()) +} + +/// Return number of decimal digits in unsigned integer +fn count_decimal_digits_uint(uint: &BigUint) -> u64 { + if uint.is_zero() { return 1; } - let uint = int.magnitude(); let mut digits = (uint.bits() as f64 / LOG2_10) as u64; // guess number of digits based on number of bits in UInt let mut num = ten_to_the(digits).to_biguint().expect("Ten to power is negative"); @@ -161,6 +165,7 @@ fn count_decimal_digits(int: &BigInt) -> u64 { digits } + /// Internal function used for rounding /// /// returns 1 if most significant digit is >= 5, otherwise 0 @@ -2071,6 +2076,73 @@ impl fmt::Debug for BigDecimal { } +/// A big-decimal wrapping a borrowed (immutable) buffer of digits +/// +/// The non-digit information like `scale` and `sign` may be changed +/// on these objects, which otherwise would require cloning the full +/// digit buffer in the BigDecimal. +/// +/// May be transformed into full BigDecimal object using the to_owned() +/// method. +/// +/// BigDecimalRefs should be preferred over using &BigDecimal for most +/// functions that need an immutable reference to a bigdecimal. +/// +#[derive(Clone, Copy, Debug)] +pub struct BigDecimalRef<'a> { + sign: Sign, + digits: &'a BigUint, + scale: i64, +} + +impl BigDecimalRef<'_> { + /// Clone digits to make this reference a full BigDecimal object + pub fn to_owned(&self) -> BigDecimal { + BigDecimal { + scale: self.scale, + int_val: BigInt::from_biguint(self.sign, self.digits.clone()), + } + } + + /// Sign of decimal + pub fn sign(&self) -> Sign { + self.sign + } + + /// Count number of decimal digits + pub fn count_digits(&self) -> u64 { + count_decimal_digits_uint(self.digits) + } + + /// Take absolute value of the decimal (non-negative sign) + pub fn abs(&self) -> Self { + Self { + sign: self.sign * self.sign, + digits: self.digits, + scale: self.scale, + } + } +} + +impl<'a> From<&'a BigDecimal> for BigDecimalRef<'a> { + fn from(n: &'a BigDecimal) -> Self { + let sign = n.int_val.sign(); + let mag = n.int_val.magnitude(); + Self { + sign: sign, + digits: mag, + scale: n.scale, + } + } +} + +impl<'a> From> for BigDecimal { + fn from(n: BigDecimalRef<'a>) -> Self { + n.to_owned() + } +} + + /// Tools to help serializing/deserializing `BigDecimal`s #[cfg(feature = "serde")] mod bigdecimal_serde { From dac45fa8f771f61ad19cc55f08ae241a48b90fd6 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Fri, 15 Sep 2023 01:00:38 -0400 Subject: [PATCH 12/77] Impl std::ops::Neg for BigDecimalRef --- src/impl_ops.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/impl_ops.rs b/src/impl_ops.rs index 8e91e60..3ca8d58 100644 --- a/src/impl_ops.rs +++ b/src/impl_ops.rs @@ -1,6 +1,9 @@ //! Implement math operations: Add,Sub, etc -use crate::BigDecimal; +use crate::{ + BigDecimal, + BigDecimalRef, +}; use crate::stdlib::ops::{ Add, AddAssign, Sub, SubAssign, @@ -439,3 +442,16 @@ impl_div_for_primitive!(i128); impl_div_for_primitive!(f32); impl_div_for_primitive!(f64); + + +impl Neg for BigDecimalRef<'_> { + type Output = Self; + + fn neg(self) -> Self::Output { + Self { + sign: self.sign.neg(), + digits: self.digits, + scale: self.scale, + } + } +} From faa6a1277c7eb641b872b556ad3e585181fd2da8 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Fri, 15 Sep 2023 01:03:35 -0400 Subject: [PATCH 13/77] Move impl Neg of BigDecimal to impl_ops.rs --- src/impl_ops.rs | 19 +++++++++++++++++++ src/lib.rs | 18 ------------------ 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/impl_ops.rs b/src/impl_ops.rs index 3ca8d58..8a02e36 100644 --- a/src/impl_ops.rs +++ b/src/impl_ops.rs @@ -444,6 +444,25 @@ impl_div_for_primitive!(f32); impl_div_for_primitive!(f64); +impl Neg for BigDecimal { + type Output = BigDecimal; + + #[inline] + fn neg(mut self) -> BigDecimal { + self.int_val = -self.int_val; + self + } +} + +impl<'a> Neg for &'a BigDecimal { + type Output = BigDecimal; + + #[inline] + fn neg(self) -> BigDecimal { + -self.clone() + } +} + impl Neg for BigDecimalRef<'_> { type Output = Self; diff --git a/src/lib.rs b/src/lib.rs index 5087476..b0eeba4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1942,24 +1942,6 @@ impl<'a, 'b> Rem<&'b BigDecimal> for &'a BigDecimal { } } -impl Neg for BigDecimal { - type Output = BigDecimal; - - #[inline] - fn neg(mut self) -> BigDecimal { - self.int_val = -self.int_val; - self - } -} - -impl<'a> Neg for &'a BigDecimal { - type Output = BigDecimal; - - #[inline] - fn neg(self) -> BigDecimal { - -self.clone() - } -} impl Signed for BigDecimal { #[inline] From 69a291574b7509739a88024f9f1510185ead41c8 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Fri, 15 Sep 2023 01:10:21 -0400 Subject: [PATCH 14/77] Move reimplementation of PartialEq into impl_cmp.rs --- src/impl_cmp.rs | 99 +++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 54 ++------------------------- 2 files changed, 103 insertions(+), 50 deletions(-) create mode 100644 src/impl_cmp.rs diff --git a/src/impl_cmp.rs b/src/impl_cmp.rs new file mode 100644 index 0000000..c46dc14 --- /dev/null +++ b/src/impl_cmp.rs @@ -0,0 +1,99 @@ +//! Implementation of comparsion operations +//! +//! Comparisons between decimals and decimal refs +//! are not directly supported as we lose some type +//! inference features at the savings of a single +//! '&' character. +//! +//! &BigDecimal and BigDecimalRef are comparible. +//! + +use crate::{ + BigDecimal, + BigDecimalRef, + Sign, +}; + +use stdlib::cmp::Ordering; +use stdlib::iter; + +impl PartialEq for BigDecimal +{ + fn eq(&self, rhs: &BigDecimal) -> bool { + self.take_ref() == rhs.take_ref() + } +} + +impl<'rhs, T> PartialEq for BigDecimalRef<'_> +where + T: Into> + Copy +{ + fn eq(&self, rhs: &T) -> bool { + let rhs: BigDecimalRef<'rhs> = (*rhs).into(); + + match (self.sign(), rhs.sign()) { + // both zero + (Sign::NoSign, Sign::NoSign) => return true, + // signs are different + (a, b) if a != b => return false, + // signs are same, do nothing + _ => {} + } + + let unscaled_int; + let scaled_int; + let trailing_zero_count; + match self.scale.cmp(&rhs.scale) { + Ordering::Greater => { + unscaled_int = self.digits; + scaled_int = rhs.digits; + trailing_zero_count = (self.scale - rhs.scale) as usize; + } + Ordering::Less => { + unscaled_int = rhs.digits; + scaled_int = self.digits; + trailing_zero_count = (rhs.scale - self.scale) as usize; + } + Ordering::Equal => return self.digits == rhs.digits, + } + + if trailing_zero_count < 20 { + let scaled_int = scaled_int * crate::ten_to_the(trailing_zero_count as u64).magnitude(); + return &scaled_int == unscaled_int; + } + + let unscaled_digits = unscaled_int.to_radix_le(10); + let scaled_digits = scaled_int.to_radix_le(10); + + // different lengths with trailing zeros + if unscaled_digits.len() != scaled_digits.len() + trailing_zero_count { + return false; + } + + // add leading zero digits to digits that need scaled + let scaled = iter::repeat(&0u8).take(trailing_zero_count).chain(scaled_digits.iter()); + + // return true if all digits are the same + unscaled_digits.iter().zip(scaled).all(|(digit_a, digit_b)| { digit_a == digit_b }) + } +} + + +#[cfg(test)] +mod test_bigintref { + use super::*; + use stdlib::ops::Neg; + + #[test] + fn test_borrow_neg_cmp() { + let x: BigDecimal = "1514932018891593.916341142773".parse().unwrap(); + let y: BigDecimal = "1514932018891593916341142773e-12".parse().unwrap(); + + assert_eq!(x, y); + + let x_ref = x.take_ref(); + assert_eq!(x_ref, &y); + assert_ne!(x_ref.neg(), x_ref); + assert_eq!(x_ref.neg().neg(), x_ref); + } +} diff --git a/src/lib.rs b/src/lib.rs index b0eeba4..03dcba6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -68,7 +68,7 @@ use self::stdlib::default::Default; use self::stdlib::hash::{Hash, Hasher}; use self::stdlib::num::{ParseFloatError, ParseIntError}; use self::stdlib::ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Rem, Sub, SubAssign}; -use self::stdlib::iter::{self, Sum}; +use self::stdlib::iter::Sum; use self::stdlib::str::FromStr; use self::stdlib::string::{String, ToString}; use self::stdlib::fmt; @@ -94,6 +94,9 @@ mod impl_convert; // Add, Sub, etc... mod impl_ops; +// PartialEq +mod impl_cmp; + // Implementations of num_traits mod impl_num; @@ -1114,55 +1117,6 @@ impl Ord for BigDecimal { } } -impl PartialEq for BigDecimal { - #[inline] - fn eq(&self, rhs: &BigDecimal) -> bool { - match (self.sign(), rhs.sign()) { - // both zero - (Sign::NoSign, Sign::NoSign) => return true, - // signs are different - (a, b) if a != b => return false, - // signs are same, do nothing - _ => {} - } - - let unscaled_int; - let scaled_int; - let trailing_zero_count; - match self.scale.cmp(&rhs.scale) { - Ordering::Greater => { - unscaled_int = &self.int_val; - scaled_int = &rhs.int_val; - trailing_zero_count = (self.scale - rhs.scale) as usize; - } - Ordering::Less => { - unscaled_int = &rhs.int_val; - scaled_int = &self.int_val; - trailing_zero_count = (rhs.scale - self.scale) as usize; - } - Ordering::Equal => return self.int_val == rhs.int_val, - } - - if trailing_zero_count < 20 { - let scaled_int = scaled_int * ten_to_the(trailing_zero_count as u64); - return &scaled_int == unscaled_int; - } - - let (_, unscaled_digits) = unscaled_int.to_radix_le(10); - let (_, scaled_digits) = scaled_int.to_radix_le(10); - - // different lengths with trailing zeros - if unscaled_digits.len() != scaled_digits.len() + trailing_zero_count { - return false; - } - - // add leading zero digits to digits that need scaled - let scaled = iter::repeat(&0u8).take(trailing_zero_count).chain(scaled_digits.iter()); - - // return true if all digits are the same - unscaled_digits.iter().zip(scaled).all(|(digit_a, digit_b)| { digit_a == digit_b }) - } -} impl Default for BigDecimal { #[inline] From 5cb081d1baeb4c97c1c5b4878759679c6d52bf40 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Fri, 15 Sep 2023 01:11:45 -0400 Subject: [PATCH 15/77] Add method BigDecimal::to_ref --- src/impl_cmp.rs | 4 ++-- src/lib.rs | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/impl_cmp.rs b/src/impl_cmp.rs index c46dc14..feeaa43 100644 --- a/src/impl_cmp.rs +++ b/src/impl_cmp.rs @@ -20,7 +20,7 @@ use stdlib::iter; impl PartialEq for BigDecimal { fn eq(&self, rhs: &BigDecimal) -> bool { - self.take_ref() == rhs.take_ref() + self.to_ref() == rhs.to_ref() } } @@ -91,7 +91,7 @@ mod test_bigintref { assert_eq!(x, y); - let x_ref = x.take_ref(); + let x_ref = x.to_ref(); assert_eq!(x_ref, &y); assert_ne!(x_ref.neg(), x_ref); assert_eq!(x_ref.neg().neg(), x_ref); diff --git a/src/lib.rs b/src/lib.rs index 03dcba6..7a9bf72 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -234,6 +234,12 @@ impl BigDecimal { } } + /// Make a BigDecimalRef of this value + pub fn to_ref<'a>(&'a self) -> BigDecimalRef<'a> { + // search for "From<&'a BigDecimal> for BigDecimalRef<'a>" + self.into() + } + /// Creates and initializes a `BigDecimal`. /// /// Decodes using `str::from_utf8` and forwards to `BigDecimal::from_str_radix`. From 6b976d3420a73bcabacc07f131cfdb0ed9c50f6d Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Fri, 15 Sep 2023 02:09:48 -0400 Subject: [PATCH 16/77] Fix compatibility with older rustc versions --- build.rs | 3 +++ src/lib.rs | 19 +++++++++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/build.rs b/build.rs index 196093d..d0a0df7 100644 --- a/build.rs +++ b/build.rs @@ -7,6 +7,9 @@ fn main() { let ac = autocfg::new(); ac.emit_rustc_version(1, 70); + // Option::zip + ac.emit_rustc_version(1, 46); + let outdir: PathBuf = std::env::var_os("OUT_DIR").unwrap().into(); write_default_precision_file(&outdir); } diff --git a/src/lib.rs b/src/lib.rs index 7a9bf72..d84ea46 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -441,20 +441,31 @@ impl BigDecimal { } /// Return this BigDecimal with the given precision, rounding if needed + #[cfg(rustc_1_46)] // Option::zip pub fn with_precision_round(&self, prec: stdlib::num::NonZeroU64, round: RoundingMode) -> BigDecimal { let digit_count = self.digits(); let new_prec = prec.get().to_i64(); let new_scale = new_prec .zip(digit_count.to_i64()) - .map(|(new_prec, old_prec)| new_prec.checked_sub(old_prec)) - .flatten() - .map(|prec_diff| self.scale.checked_add(prec_diff)) - .flatten() + .and_then(|(new_prec, old_prec)| new_prec.checked_sub(old_prec)) + .and_then(|prec_diff| self.scale.checked_add(prec_diff)) .expect("precision overflow"); self.with_scale_round(new_scale, round) } + #[cfg(not(rustc_1_46))] + pub fn with_precision_round(&self, prec: stdlib::num::NonZeroU64, round: RoundingMode) -> BigDecimal { + let new_scale = self.digits().to_i64().and_then( + |old_prec| { + prec.get().to_i64().and_then( + |new_prec| { new_prec.checked_sub(old_prec) })}) + .and_then(|prec_diff| self.scale.checked_add(prec_diff)) + .expect("precision overflow"); + + self.with_scale_round(new_scale, round) + } + /// Return the sign of the `BigDecimal` as `num::bigint::Sign`. /// /// ``` From de40b5a879912def11fdde6e94920d157d361e66 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 23 Sep 2023 23:50:05 -0400 Subject: [PATCH 17/77] Add BigDecimalRef::as_parts method --- src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index d84ea46..0473b54 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2067,6 +2067,12 @@ impl BigDecimalRef<'_> { count_decimal_digits_uint(self.digits) } + /// Split into components + #[allow(dead_code)] + pub(crate) fn as_parts(&self) -> (Sign, i64, &BigUint) { + (self.sign, self.scale, self.digits) + } + /// Take absolute value of the decimal (non-negative sign) pub fn abs(&self) -> Self { Self { From 2a054bb8d3b15c7a45ead5e01fdd5392bb64ac49 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Fri, 15 Sep 2023 21:34:12 -0400 Subject: [PATCH 18/77] Move property-test configuration to build.rs --- build.rs | 3 +++ scripts/bigdecimal-property-tests | 2 +- src/lib.rs | 16 +++++++++++----- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/build.rs b/build.rs index d0a0df7..ab99787 100644 --- a/build.rs +++ b/build.rs @@ -10,6 +10,9 @@ fn main() { // Option::zip ac.emit_rustc_version(1, 46); + // Remove this comment if enabled proptests + // ::PROPERTY-TESTS:: autocfg::emit("property_tests"); + let outdir: PathBuf = std::env::var_os("OUT_DIR").unwrap().into(); write_default_precision_file(&outdir); } diff --git a/scripts/bigdecimal-property-tests b/scripts/bigdecimal-property-tests index bce2e79..8920de8 100755 --- a/scripts/bigdecimal-property-tests +++ b/scripts/bigdecimal-property-tests @@ -9,7 +9,7 @@ sed -i.bak -e 's|# PROPERTY-TESTS: ||' Cargo.toml # include the property-test file in lib.rs -sed -i.bak -e 's|// ::PROPERTY-TESTS:: ||' src/lib.rs +sed -i.bak -e 's|// ::PROPERTY-TESTS:: ||' build.rs # Run commands "$@" diff --git a/src/lib.rs b/src/lib.rs index 0473b54..840ec55 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3356,8 +3356,14 @@ mod test_with_scale_round { include!("lib.tests.with_scale_round.rs"); } -// enable these tests with scripts/bigdecimal-property-tests -// ::PROPERTY-TESTS:: #[cfg(test)] #[macro_use] extern crate proptest; -// ::PROPERTY-TESTS:: #[cfg(test)] mod property_tests { -// ::PROPERTY-TESTS:: use super::*; use paste::paste; -// ::PROPERTY-TESTS:: include!("lib.tests.property-tests.rs"); } + +#[cfg(all(test, property_tests))] +extern crate proptest; + +#[cfg(all(test, property_tests))] +mod proptests { + use super::*; + use paste::paste; + + include!("lib.tests.property-tests.rs"); +} From 78a11ef97a736fdc15efa125ec5418400e6f1003 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Fri, 15 Sep 2023 20:41:08 -0400 Subject: [PATCH 19/77] Add math Context struct --- src/context.rs | 181 +++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 15 +++- 2 files changed, 193 insertions(+), 3 deletions(-) create mode 100644 src/context.rs diff --git a/src/context.rs b/src/context.rs new file mode 100644 index 0000000..32c66ac --- /dev/null +++ b/src/context.rs @@ -0,0 +1,181 @@ +//! Define arithmetical context +//! + +use crate::rounding::RoundingMode; +use stdlib::num::NonZeroU64; +use stdlib; + +use num_traits::{ToPrimitive, Zero}; + +use crate::{ + Sign, + BigDecimal, + BigDecimalRef +}; + +// const DEFAULT_PRECISION: u64 = ${RUST_BIGDECIMAL_DEFAULT_PRECISION} or 100; +include!(concat!(env!("OUT_DIR"), "/default_precision.rs")); + +/// Mathematical Context +/// +/// Stores rules for numerical operations, such as how to round and +/// number of digits to keep. +/// +/// Defaults are defined at compile time, determined by environment +/// variables: +/// +/// | Variable | Descripiton | default | +/// |-----------------------------------------|-----------------|----------| +/// | `RUST_BIGDECIMAL_DEFAULT_PRECISION` | digit precision | 100 | +/// | `RUST_BIGDECIMAL_DEFAULT_ROUNDING_MODE` | rounding-mode | HalfEven | +/// +/// It is recommended that the user set explict values of a Context and *not* +/// rely on compile time constants, but the option is there if necessary. +/// +#[derive(Debug, Clone)] +pub struct Context { + /// total number of digits + precision: NonZeroU64, + /// how to round + rounding: RoundingMode, +} + +impl Context { + /// Create context with precision and rounding mode + pub fn new(precision: NonZeroU64, rounding: RoundingMode) -> Self { + Context { + precision: precision, + rounding: rounding, + } + } + + /// Copy context with new precision value + pub fn with_precision(&self, precision: NonZeroU64) -> Self { + Self { + precision: precision, + ..*self + } + } + + /// Copy context with new precision value + pub fn with_prec(&self, precision: T) -> Option { + precision + .to_u64() + .map(NonZeroU64::new) + .flatten() + .map(|prec| { + Self { + precision: prec, + ..*self + } + }) + } + + /// Copy context with new rounding mode + pub fn with_rounding_mode(&self, mode: RoundingMode) -> Self { + Self { + rounding: mode, + ..*self + } + } + + /// Return maximum precision + pub fn precision(&self) -> NonZeroU64 { + self.precision + } + + /// Return rounding mode + pub fn rounding_mode(&self) -> RoundingMode { + self.rounding + } + + /// Round digits x and y with the rounding mode + pub(crate) fn round_pair(&self, sign: Sign, x: u8, y: u8, trailing_zeros: bool) -> u8 { + self.rounding.round_pair(sign, (x, y), trailing_zeros) + } + + /// Round digits x and y with the rounding mode + #[allow(dead_code)] + pub(crate) fn round_pair_with_carry(&self, sign: Sign, x: u8, y: u8, trailing_zeros: bool, carry: &mut u8) -> u8 { + let r = self.round_pair(sign, x, y, trailing_zeros); + if r == 10 { + *carry = 1; + 0 + } else { + r + } + } +} + +impl stdlib::default::Default for Context { + fn default() -> Self { + Self { + precision: NonZeroU64::new(DEFAULT_PRECISION).unwrap(), + rounding: RoundingMode::HalfEven, + } + } +} + +impl Context { + /// Add two big digit references + pub fn add_refs<'a, 'b, A, B>(&self, a: A, b: B) -> BigDecimal + where + A: Into>, + B: Into>, + { + let mut sum = BigDecimal::zero(); + self.add_refs_into(a, b, &mut sum); + sum + } + + /// Add two decimal refs, storing value in dest + pub fn add_refs_into<'a, 'b, A, B>(&self, a: A, b: B, dest: &mut BigDecimal) + where + A: Into>, + B: Into>, + { + let a = a.into(); + let b = b.into(); + let sum = a.to_owned() + b.to_owned(); + *dest = sum.with_precision_round(self.precision, self.rounding) + } +} + + +#[cfg(test)] +mod test_context { + use super::*; + + #[test] + fn contstructor_and_setters() { + let ctx = Context::default(); + let c = ctx.with_prec(44).unwrap(); + assert_eq!(c.precision.get(), 44); + assert_eq!(c.rounding, RoundingMode::HalfEven); + + let c = c.with_rounding_mode(RoundingMode::Down); + assert_eq!(c.precision.get(), 44); + assert_eq!(c.rounding, RoundingMode::Down); + } + + #[test] + fn sum_two_references() { + use stdlib::ops::Neg; + + let ctx = Context::default(); + let a: BigDecimal = "209682.134972197168613072130300".parse().unwrap(); + let b: BigDecimal = "3.0782968222271332463325639E-12".parse().unwrap(); + + let sum = ctx.add_refs(&a, &b); + assert_eq!(sum, "209682.1349721971716913689525271332463325639".parse().unwrap()); + + // make negative copy of b without cloning values + let neg_b = b.to_ref().neg(); + + let sum = ctx.add_refs(&a, neg_b); + assert_eq!(sum, "209682.1349721971655347753080728667536674361".parse().unwrap()); + + let sum = ctx.with_prec(27).unwrap().with_rounding_mode(RoundingMode::Up).add_refs(&a, neg_b); + assert_eq!(sum, "209682.134972197165534775309".parse().unwrap()); + } +} diff --git a/src/lib.rs b/src/lib.rs index fe48d7e..d3a44ea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -104,13 +104,22 @@ mod parsing; pub mod rounding; pub use rounding::RoundingMode; +// Mathematical context +mod context; +pub use context::Context; + /// Return 10^pow /// /// Try to calculate this with fewest number of allocations /// fn ten_to_the(pow: u64) -> BigInt { + ten_to_the_uint(pow).into() +} + +/// Return 10^pow +fn ten_to_the_uint(pow: u64) -> BigUint { if pow < 20 { - return BigInt::from(10u64.pow(pow as u32)); + return BigUint::from(10u64.pow(pow as u32)); } // linear case of 10^pow = 10^(19 * count + rem) @@ -120,7 +129,7 @@ fn ten_to_the(pow: u64) -> BigInt { // count factors of 19 let (count, rem) = pow.div_rem(&19); - let mut res = BigInt::from(ten_to_nineteen); + let mut res = BigUint::from(ten_to_nineteen); for _ in 1..count { res *= ten_to_nineteen; } @@ -133,7 +142,7 @@ fn ten_to_the(pow: u64) -> BigInt { // use recursive algorithm where linear case might be too slow let (quotient, rem) = pow.div_rem(&16); - let x = ten_to_the(quotient); + let x = ten_to_the_uint(quotient); let x2 = &x * &x; let x4 = &x2 * &x2; From 020ded4c4acf36775d7bcff6b5e9fbb46a6ace4a Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Mon, 25 Sep 2023 22:25:36 -0400 Subject: [PATCH 20/77] Add subcommands to bigdecimal-property-tests --- .gitlab-ci.yml | 6 ++-- scripts/bigdecimal-property-tests | 48 +++++++++++++++++++++++++------ src/lib.rs | 1 + 3 files changed, 43 insertions(+), 12 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2360805..bbc0d0a 100755 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -118,7 +118,7 @@ cargo:check: <<: *script-cargo-check script: # enable property tests for the stable 'pipeline' - - scripts/bigdecimal-property-tests cargo check --all-targets + - scripts/bigdecimal-property-tests run cargo check --all-targets cargo:clippy: stage: check @@ -158,7 +158,7 @@ cargo:build-stable: <<: *script-cargo-build script: # enable property tests for the stable 'pipeline' - - scripts/bigdecimal-property-tests cargo build --all-targets + - scripts/bigdecimal-property-tests run cargo build --all-targets cargo:test-stable: @@ -171,7 +171,7 @@ cargo:test-stable: <<: *script-cargo-test script: # enable property tests for the stable 'pipeline' - - scripts/bigdecimal-property-tests cargo test + - scripts/bigdecimal-property-tests run cargo test cargo:build:no-std: diff --git a/scripts/bigdecimal-property-tests b/scripts/bigdecimal-property-tests index 8920de8..66b120f 100755 --- a/scripts/bigdecimal-property-tests +++ b/scripts/bigdecimal-property-tests @@ -5,15 +5,45 @@ # Tests are defined in src/lib.tests.property-test.rs # -# enable property-test dependencies in Cargo -sed -i.bak -e 's|# PROPERTY-TESTS: ||' Cargo.toml +function enable_property_tests() { + # enable property-test dependencies in Cargo + sed -i.bak -e 's|# PROPERTY-TESTS: ||' Cargo.toml -# include the property-test file in lib.rs -sed -i.bak -e 's|// ::PROPERTY-TESTS:: ||' build.rs + # add the property-test configuration in build.rs + sed -i.bak -e 's|// ::PROPERTY-TESTS:: ||' build.rs +} -# Run commands -"$@" -# Restore Cargo.toml with backup -mv Cargo.toml.bak Cargo.toml -mv src/lib.rs.bak src/lib.rs +function restore_disabled_property_tests() { + # Restore Cargo.toml with backup + mv Cargo.toml.bak Cargo.toml + mv build.rs.bak build.rs +} + + +DEFAULT_CMD=run +CMD=${1:-$DEFAULT_CMD} +shift + +case "${CMD}" in + run) + enable_property_tests + # Run commands + "$@" + restore_disabled_property_tests + ;; + + test) + enable_property_tests + cargo test $@ + restore_disabled_property_tests + ;; + + enable) + enable_property_tests + ;; + + disable) + restore_disabled_property_tests + ;; +esac diff --git a/src/lib.rs b/src/lib.rs index 840ec55..fe48d7e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3364,6 +3364,7 @@ extern crate proptest; mod proptests { use super::*; use paste::paste; + use proptest::*; include!("lib.tests.property-tests.rs"); } From 5e0f293690db5b2fd43dba10af26b26457b8866d Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sun, 24 Sep 2023 14:17:38 -0400 Subject: [PATCH 21/77] Add and use DEFAULT_ROUNDING_MODE compile-time configuration --- build.rs | 18 +++++++++++++++--- src/context.rs | 4 +++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/build.rs b/build.rs index ab99787..3c47214 100644 --- a/build.rs +++ b/build.rs @@ -1,7 +1,7 @@ -#![allow(clippy::style)] use std::env; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; + fn main() { let ac = autocfg::new(); @@ -15,11 +15,12 @@ fn main() { let outdir: PathBuf = std::env::var_os("OUT_DIR").unwrap().into(); write_default_precision_file(&outdir); + write_default_rounding_mode(&outdir); } /// Create default_precision.rs, containg definition of constant DEFAULT_PRECISION loaded in src/lib.rs -fn write_default_precision_file(outdir: &PathBuf) { +fn write_default_precision_file(outdir: &Path) { let env_var = env::var("RUST_BIGDECIMAL_DEFAULT_PRECISION").unwrap_or_else(|_| "100".to_owned()); println!("cargo:rerun-if-env-changed=RUST_BIGDECIMAL_DEFAULT_PRECISION"); @@ -34,3 +35,14 @@ fn write_default_precision_file(outdir: &PathBuf) { std::fs::write(rust_file_path, rust_file_contents).unwrap(); } + +/// Create default_rounding_mode.rs, using value of RUST_BIGDECIMAL_DEFAULT_ROUNDING_MODE environment variable +fn write_default_rounding_mode(outdir: &Path) { + let rounding_mode_name = env::var("RUST_BIGDECIMAL_DEFAULT_ROUNDING_MODE").unwrap_or_else(|_| "HalfEven".to_owned()); + println!("cargo:rerun-if-env-changed=RUST_BIGDECIMAL_DEFAULT_ROUNDING_MODE"); + + let rust_file_path = outdir.join("default_rounding_mode.rs"); + let rust_file_contents = format!("const DEFAULT_ROUNDING_MODE: RoundingMode = RoundingMode::{};", rounding_mode_name); + + std::fs::write(rust_file_path, rust_file_contents).unwrap(); +} diff --git a/src/context.rs b/src/context.rs index 32c66ac..abab31f 100644 --- a/src/context.rs +++ b/src/context.rs @@ -15,6 +15,8 @@ use crate::{ // const DEFAULT_PRECISION: u64 = ${RUST_BIGDECIMAL_DEFAULT_PRECISION} or 100; include!(concat!(env!("OUT_DIR"), "/default_precision.rs")); +// const DEFAULT_ROUNDING_MODE: RoundingMode = ${RUST_BIGDECIMAL_DEFAULT_ROUNDING_MODE} or HalfUp; +include!(concat!(env!("OUT_DIR"), "/default_rounding_mode.rs")); /// Mathematical Context /// @@ -111,7 +113,7 @@ impl stdlib::default::Default for Context { fn default() -> Self { Self { precision: NonZeroU64::new(DEFAULT_PRECISION).unwrap(), - rounding: RoundingMode::HalfEven, + rounding: DEFAULT_ROUNDING_MODE, } } } From 85483d94e722a17530f7bfbcd2c8a65e50f80249 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Mon, 25 Sep 2023 01:06:37 -0400 Subject: [PATCH 22/77] Move impl Add & AddAssign to impl_ops_add.rs --- src/impl_ops_add.rs | 180 ++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 165 +--------------------------------------- 2 files changed, 181 insertions(+), 164 deletions(-) create mode 100644 src/impl_ops_add.rs diff --git a/src/impl_ops_add.rs b/src/impl_ops_add.rs new file mode 100644 index 0000000..5020f03 --- /dev/null +++ b/src/impl_ops_add.rs @@ -0,0 +1,180 @@ +//! Addition operator trait implementation +//! + +use crate::{ + Sign, + BigDecimal, + BigDecimalRef, +}; + +use crate::stdlib::{ + ops::{Add, AddAssign, Neg}, + cmp::Ordering, +}; + +use num_bigint::{BigInt, BigUint}; +use crate::ten_to_the; + +impl Add for BigDecimal { + type Output = BigDecimal; + + #[inline] + fn add(self, rhs: BigDecimal) -> BigDecimal { + let mut lhs = self; + + match lhs.scale.cmp(&rhs.scale) { + Ordering::Equal => { + lhs.int_val += rhs.int_val; + lhs + } + Ordering::Less => lhs.take_and_scale(rhs.scale) + rhs, + Ordering::Greater => rhs.take_and_scale(lhs.scale) + lhs, + } + } +} + +impl<'a> Add<&'a BigDecimal> for BigDecimal { + type Output = BigDecimal; + + #[inline] + fn add(self, rhs: &'a BigDecimal) -> BigDecimal { + let mut lhs = self; + + match lhs.scale.cmp(&rhs.scale) { + Ordering::Equal => { + lhs.int_val += &rhs.int_val; + lhs + } + Ordering::Less => lhs.take_and_scale(rhs.scale) + rhs, + Ordering::Greater => rhs.with_scale(lhs.scale) + lhs, + } + } +} + +impl<'a> Add for &'a BigDecimal { + type Output = BigDecimal; + + #[inline] + fn add(self, rhs: BigDecimal) -> BigDecimal { + rhs + self + } +} + +impl<'a, 'b> Add<&'b BigDecimal> for &'a BigDecimal { + type Output = BigDecimal; + + #[inline] + fn add(self, rhs: &BigDecimal) -> BigDecimal { + let lhs = self; + match self.scale.cmp(&rhs.scale) { + Ordering::Less => lhs.with_scale(rhs.scale) + rhs, + Ordering::Greater => rhs.with_scale(lhs.scale) + lhs, + Ordering::Equal => BigDecimal::new(lhs.int_val.clone() + &rhs.int_val, lhs.scale), + } + } +} + +impl Add for BigDecimal { + type Output = BigDecimal; + + #[inline] + fn add(self, rhs: BigInt) -> BigDecimal { + let mut lhs = self; + + match lhs.scale.cmp(&0) { + Ordering::Equal => { + lhs.int_val += rhs; + lhs + } + Ordering::Greater => { + lhs.int_val += rhs * ten_to_the(lhs.scale as u64); + lhs + } + Ordering::Less => lhs.take_and_scale(0) + rhs, + } + } +} + +impl<'a> Add<&'a BigInt> for BigDecimal { + type Output = BigDecimal; + + #[inline] + fn add(self, rhs: &BigInt) -> BigDecimal { + let mut lhs = self; + + match lhs.scale.cmp(&0) { + Ordering::Equal => { + lhs.int_val += rhs; + lhs + } + Ordering::Greater => { + lhs.int_val += rhs * ten_to_the(lhs.scale as u64); + lhs + } + Ordering::Less => lhs.take_and_scale(0) + rhs, + } + } +} + +impl<'a> Add for &'a BigDecimal { + type Output = BigDecimal; + + #[inline] + fn add(self, rhs: BigInt) -> BigDecimal { + BigDecimal::new(rhs, 0) + self + } +} + +impl<'a, 'b> Add<&'a BigInt> for &'b BigDecimal { + type Output = BigDecimal; + + #[inline] + fn add(self, rhs: &BigInt) -> BigDecimal { + self.with_scale(0) + rhs + } +} + +forward_val_assignop!(impl AddAssign for BigDecimal, add_assign); + +impl<'a> AddAssign<&'a BigDecimal> for BigDecimal { + #[inline] + fn add_assign(&mut self, rhs: &BigDecimal) { + match self.scale.cmp(&rhs.scale) { + Ordering::Less => { + let scaled = self.with_scale(rhs.scale); + self.int_val = scaled.int_val + &rhs.int_val; + self.scale = rhs.scale; + } + Ordering::Greater => { + let scaled = rhs.with_scale(self.scale); + self.int_val += scaled.int_val; + } + Ordering::Equal => { + self.int_val += &rhs.int_val; + } + } + } +} + +impl AddAssign for BigDecimal { + #[inline] + fn add_assign(&mut self, rhs: BigInt) { + *self += BigDecimal::new(rhs, 0) + } +} + +impl<'a> AddAssign<&'a BigInt> for BigDecimal { + #[inline] + fn add_assign(&mut self, rhs: &BigInt) { + match self.scale.cmp(&0) { + Ordering::Equal => self.int_val += rhs, + Ordering::Greater => self.int_val += rhs * ten_to_the(self.scale as u64), + Ordering::Less => { + // *self += BigDecimal::new(rhs, 0) + self.int_val *= ten_to_the((-self.scale) as u64); + self.int_val += rhs; + self.scale = 0; + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index d3a44ea..d2b70e8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,6 +93,7 @@ extern crate paste; mod impl_convert; // Add, Sub, etc... mod impl_ops; +mod impl_ops_add; // PartialEq mod impl_cmp; @@ -1171,170 +1172,6 @@ impl One for BigDecimal { } -impl Add for BigDecimal { - type Output = BigDecimal; - - #[inline] - fn add(self, rhs: BigDecimal) -> BigDecimal { - let mut lhs = self; - - match lhs.scale.cmp(&rhs.scale) { - Ordering::Equal => { - lhs.int_val += rhs.int_val; - lhs - } - Ordering::Less => lhs.take_and_scale(rhs.scale) + rhs, - Ordering::Greater => rhs.take_and_scale(lhs.scale) + lhs, - } - } -} - -impl<'a> Add<&'a BigDecimal> for BigDecimal { - type Output = BigDecimal; - - #[inline] - fn add(self, rhs: &'a BigDecimal) -> BigDecimal { - let mut lhs = self; - - match lhs.scale.cmp(&rhs.scale) { - Ordering::Equal => { - lhs.int_val += &rhs.int_val; - lhs - } - Ordering::Less => lhs.take_and_scale(rhs.scale) + rhs, - Ordering::Greater => rhs.with_scale(lhs.scale) + lhs, - } - } -} - -impl<'a> Add for &'a BigDecimal { - type Output = BigDecimal; - - #[inline] - fn add(self, rhs: BigDecimal) -> BigDecimal { - rhs + self - } -} - -impl<'a, 'b> Add<&'b BigDecimal> for &'a BigDecimal { - type Output = BigDecimal; - - #[inline] - fn add(self, rhs: &BigDecimal) -> BigDecimal { - let lhs = self; - match self.scale.cmp(&rhs.scale) { - Ordering::Less => lhs.with_scale(rhs.scale) + rhs, - Ordering::Greater => rhs.with_scale(lhs.scale) + lhs, - Ordering::Equal => BigDecimal::new(lhs.int_val.clone() + &rhs.int_val, lhs.scale), - } - } -} - -impl Add for BigDecimal { - type Output = BigDecimal; - - #[inline] - fn add(self, rhs: BigInt) -> BigDecimal { - let mut lhs = self; - - match lhs.scale.cmp(&0) { - Ordering::Equal => { - lhs.int_val += rhs; - lhs - } - Ordering::Greater => { - lhs.int_val += rhs * ten_to_the(lhs.scale as u64); - lhs - } - Ordering::Less => lhs.take_and_scale(0) + rhs, - } - } -} - -impl<'a> Add<&'a BigInt> for BigDecimal { - type Output = BigDecimal; - - #[inline] - fn add(self, rhs: &BigInt) -> BigDecimal { - let mut lhs = self; - - match lhs.scale.cmp(&0) { - Ordering::Equal => { - lhs.int_val += rhs; - lhs - } - Ordering::Greater => { - lhs.int_val += rhs * ten_to_the(lhs.scale as u64); - lhs - } - Ordering::Less => lhs.take_and_scale(0) + rhs, - } - } -} - -impl<'a> Add for &'a BigDecimal { - type Output = BigDecimal; - - #[inline] - fn add(self, rhs: BigInt) -> BigDecimal { - BigDecimal::new(rhs, 0) + self - } -} - -impl<'a, 'b> Add<&'a BigInt> for &'b BigDecimal { - type Output = BigDecimal; - - #[inline] - fn add(self, rhs: &BigInt) -> BigDecimal { - self.with_scale(0) + rhs - } -} - -forward_val_assignop!(impl AddAssign for BigDecimal, add_assign); - -impl<'a> AddAssign<&'a BigDecimal> for BigDecimal { - #[inline] - fn add_assign(&mut self, rhs: &BigDecimal) { - match self.scale.cmp(&rhs.scale) { - Ordering::Less => { - let scaled = self.with_scale(rhs.scale); - self.int_val = scaled.int_val + &rhs.int_val; - self.scale = rhs.scale; - } - Ordering::Greater => { - let scaled = rhs.with_scale(self.scale); - self.int_val += scaled.int_val; - } - Ordering::Equal => { - self.int_val += &rhs.int_val; - } - } - } -} - -impl AddAssign for BigDecimal { - #[inline] - fn add_assign(&mut self, rhs: BigInt) { - *self += BigDecimal::new(rhs, 0) - } -} - -impl<'a> AddAssign<&'a BigInt> for BigDecimal { - #[inline] - fn add_assign(&mut self, rhs: &BigInt) { - match self.scale.cmp(&0) { - Ordering::Equal => self.int_val += rhs, - Ordering::Greater => self.int_val += rhs * ten_to_the(self.scale as u64), - Ordering::Less => { - // *self += BigDecimal::new(rhs, 0) - self.int_val *= ten_to_the((-self.scale) as u64); - self.int_val += rhs; - self.scale = 0; - } - } - } -} - impl Sub for BigDecimal { type Output = BigDecimal; From d72efad0deefa0334072a29c7db4c0a2f6c4af7d Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Mon, 25 Sep 2023 21:39:23 -0400 Subject: [PATCH 23/77] Move and expand addition tests in impl_ops_add.rs --- src/impl_ops_add.rs | 101 ++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 28 ------------ 2 files changed, 101 insertions(+), 28 deletions(-) diff --git a/src/impl_ops_add.rs b/src/impl_ops_add.rs index 5020f03..0245311 100644 --- a/src/impl_ops_add.rs +++ b/src/impl_ops_add.rs @@ -178,3 +178,104 @@ impl<'a> AddAssign<&'a BigInt> for BigDecimal { } } } + + +#[cfg(test)] +mod test { + use super::*; + use paste::paste; + + macro_rules! impl_case { + ($name:ident: $a:literal + $b:literal => $c:literal ) => { + #[test] + fn $name() { + let mut a: BigDecimal = $a.parse().unwrap(); + let b: BigDecimal = $b.parse().unwrap(); + let c: BigDecimal = $c.parse().unwrap(); + + assert_eq!(a.clone() + b.clone(), c); + + assert_eq!(a.clone() + &b, c); + assert_eq!(&a + b.clone(), c); + assert_eq!(&a + &b, c); + + a += b; + assert_eq!(a, c); + } + }; + } + + impl_case!(case_1234en2_1234en3: "12.34" + "1.234" => "13.574"); + impl_case!(case_1234en2_n1234en3: "12.34" + "-1.234" => "11.106"); + impl_case!(case_1234en2_n1234en2: "12.34" + "-12.34" => "0"); + impl_case!(case_1234e6_1234en6: "1234e6" + "1234e-6" => "1234000000.001234"); + impl_case!(case_1234en6_1234e6: "1234e6" + "1234e-6" => "1234000000.001234"); + impl_case!(case_18446744073709551616_1: "18446744073709551616.0" + "1" => "18446744073709551617"); + impl_case!(case_184467440737e3380_1: "184467440737e3380" + "0" => "184467440737e3380"); + + + #[cfg(property_tests)] + mod prop { + use super::*; + use proptest::*; + use num_traits::FromPrimitive; + + proptest! { + #[test] + fn add_refs_and_owners(f: f32, g: f32) { + // ignore non-normal numbers + prop_assume!(f.is_normal()); + prop_assume!(g.is_normal()); + + let a = BigDecimal::from_f32(f).unwrap(); + let b = BigDecimal::from_f32(g).unwrap(); + let own_plus_ref = a.clone() + &b; + let ref_plus_own = &a + b.clone(); + + let mut c = a.clone(); + c += &b; + + let mut d = a.clone(); + d += b; + + prop_assert_eq!(&own_plus_ref, &ref_plus_own); + prop_assert_eq!(&c, &ref_plus_own); + prop_assert_eq!(&d, &ref_plus_own); + } + + #[test] + fn addition_is_communative(f: f32, g: f32) { + // ignore non-normal numbers + prop_assume!(f.is_normal()); + prop_assume!(g.is_normal()); + + let a = BigDecimal::from_f32(f).unwrap(); + let b = BigDecimal::from_f32(g).unwrap(); + let a_plus_b = &a + &b; + let b_plus_a = &b + &a; + + prop_assert_eq!(a_plus_b, b_plus_a) + } + + #[test] + fn addition_is_associative(f: f32, g: f32, h: f32) { + // ignore non-normal numbers + prop_assume!(f.is_normal()); + prop_assume!(g.is_normal()); + prop_assume!(h.is_normal()); + + let a = BigDecimal::from_f32(f).unwrap(); + let b = BigDecimal::from_f32(g).unwrap(); + let c = BigDecimal::from_f32(h).unwrap(); + + let ab = &a + &b; + let ab_c = ab + &c; + + let bc = &b + &c; + let a_bc = a + bc; + + prop_assert_eq!(ab_c, a_bc) + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index d2b70e8..050abc0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2288,34 +2288,6 @@ mod bigdecimal_tests { assert!(BigDecimal::try_from(f64::NAN).is_err()); } - #[test] - fn test_add() { - let vals = vec![ - ("12.34", "1.234", "13.574"), - ("12.34", "-1.234", "11.106"), - ("1234e6", "1234e-6", "1234000000.001234"), - ("1234e-6", "1234e6", "1234000000.001234"), - ("18446744073709551616.0", "1", "18446744073709551617"), - ("184467440737e3380", "0", "184467440737e3380"), - ]; - - for &(x, y, z) in vals.iter() { - - let mut a = BigDecimal::from_str(x).unwrap(); - let b = BigDecimal::from_str(y).unwrap(); - let c = BigDecimal::from_str(z).unwrap(); - - assert_eq!(a.clone() + b.clone(), c); - - assert_eq!(a.clone() + &b, c); - assert_eq!(&a + b.clone(), c); - assert_eq!(&a + &b, c); - - a += b; - assert_eq!(a, c); - } - } - #[test] fn test_sub() { let vals = vec![ From 3d119c837f0ea391cb5d4cbdd1f0c3018c8c818e Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Mon, 25 Sep 2023 01:50:17 -0400 Subject: [PATCH 24/77] Move impl Sub and SubAssign into impl_ops_sub.rs --- src/impl_ops_sub.rs | 185 ++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 169 +--------------------------------------- 2 files changed, 187 insertions(+), 167 deletions(-) create mode 100644 src/impl_ops_sub.rs diff --git a/src/impl_ops_sub.rs b/src/impl_ops_sub.rs new file mode 100644 index 0000000..10a460d --- /dev/null +++ b/src/impl_ops_sub.rs @@ -0,0 +1,185 @@ +//! +//! Multiplication operator trait implementation +//! + +use crate::{ + Sign, + BigDecimal, + BigDecimalRef, +}; + +use crate::stdlib::{ + mem::swap, + ops::{Sub, SubAssign, Neg}, + cmp::{self, Ordering}, +}; + +use num_bigint::{BigInt, BigUint}; +use num_traits::{Zero, One}; +use crate::ten_to_the; + +impl Sub for BigDecimal { + type Output = BigDecimal; + + #[inline] + fn sub(self, rhs: BigDecimal) -> BigDecimal { + let mut lhs = self; + let scale = cmp::max(lhs.scale, rhs.scale); + + match lhs.scale.cmp(&rhs.scale) { + Ordering::Equal => { + lhs.int_val -= rhs.int_val; + lhs + } + Ordering::Less => lhs.take_and_scale(scale) - rhs, + Ordering::Greater => lhs - rhs.take_and_scale(scale), + } + } +} + +impl<'a> Sub<&'a BigDecimal> for BigDecimal { + type Output = BigDecimal; + + #[inline] + fn sub(self, rhs: &BigDecimal) -> BigDecimal { + let mut lhs = self; + let scale = cmp::max(lhs.scale, rhs.scale); + + match lhs.scale.cmp(&rhs.scale) { + Ordering::Equal => { + lhs.int_val -= &rhs.int_val; + lhs + } + Ordering::Less => lhs.take_and_scale(rhs.scale) - rhs, + Ordering::Greater => lhs - rhs.with_scale(scale), + } + } +} + +impl<'a> Sub for &'a BigDecimal { + type Output = BigDecimal; + + #[inline] + fn sub(self, rhs: BigDecimal) -> BigDecimal { + -(rhs - self) + } +} + +impl<'a, 'b> Sub<&'b BigDecimal> for &'a BigDecimal { + type Output = BigDecimal; + + #[inline] + fn sub(self, rhs: &BigDecimal) -> BigDecimal { + match self.scale.cmp(&rhs.scale) { + Ordering::Greater => { + let rhs = rhs.with_scale(self.scale); + self - rhs + } + Ordering::Less => self.with_scale(rhs.scale) - rhs, + Ordering::Equal => BigDecimal::new(&self.int_val - &rhs.int_val, self.scale), + } + } +} + +impl Sub for BigDecimal { + type Output = BigDecimal; + + #[inline] + fn sub(self, rhs: BigInt) -> BigDecimal { + let mut lhs = self; + + match lhs.scale.cmp(&0) { + Ordering::Equal => { + lhs.int_val -= rhs; + lhs + } + Ordering::Greater => { + lhs.int_val -= rhs * ten_to_the(lhs.scale as u64); + lhs + } + Ordering::Less => lhs.take_and_scale(0) - rhs, + } + } +} + +impl<'a> Sub<&'a BigInt> for BigDecimal { + type Output = BigDecimal; + + #[inline] + fn sub(self, rhs: &BigInt) -> BigDecimal { + let mut lhs = self; + + match lhs.scale.cmp(&0) { + Ordering::Equal => { + lhs.int_val -= rhs; + lhs + } + Ordering::Greater => { + lhs.int_val -= rhs * ten_to_the(lhs.scale as u64); + lhs + } + Ordering::Less => lhs.take_and_scale(0) - rhs, + } + } +} + +impl<'a> Sub for &'a BigDecimal { + type Output = BigDecimal; + + #[inline] + fn sub(self, rhs: BigInt) -> BigDecimal { + BigDecimal::new(rhs, 0) - self + } +} + +impl<'a, 'b> Sub<&'a BigInt> for &'b BigDecimal { + type Output = BigDecimal; + + #[inline] + fn sub(self, rhs: &BigInt) -> BigDecimal { + self.with_scale(0) - rhs + } +} + +forward_val_assignop!(impl SubAssign for BigDecimal, sub_assign); + +impl<'a> SubAssign<&'a BigDecimal> for BigDecimal { + #[inline] + fn sub_assign(&mut self, rhs: &BigDecimal) { + match self.scale.cmp(&rhs.scale) { + Ordering::Less => { + let lhs = self.with_scale(rhs.scale); + self.int_val = lhs.int_val - &rhs.int_val; + self.scale = rhs.scale; + } + Ordering::Greater => { + self.int_val -= rhs.with_scale(self.scale).int_val; + } + Ordering::Equal => { + self.int_val = &self.int_val - &rhs.int_val; + } + } + } +} + +impl SubAssign for BigDecimal { + #[inline(always)] + fn sub_assign(&mut self, rhs: BigInt) { + *self -= BigDecimal::new(rhs, 0) + } +} + +impl<'a> SubAssign<&'a BigInt> for BigDecimal { + #[inline(always)] + fn sub_assign(&mut self, rhs: &BigInt) { + match self.scale.cmp(&0) { + Ordering::Equal => SubAssign::sub_assign(&mut self.int_val, rhs), + Ordering::Greater => SubAssign::sub_assign(&mut self.int_val, rhs * ten_to_the(self.scale as u64)), + Ordering::Less => { + self.int_val *= ten_to_the((-self.scale) as u64); + SubAssign::sub_assign(&mut self.int_val, rhs); + self.scale = 0; + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 050abc0..c5a0cbe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -67,7 +67,7 @@ use self::stdlib::convert::TryFrom; use self::stdlib::default::Default; use self::stdlib::hash::{Hash, Hasher}; use self::stdlib::num::{ParseFloatError, ParseIntError}; -use self::stdlib::ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Rem, Sub, SubAssign}; +use self::stdlib::ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Rem, Sub}; use self::stdlib::iter::Sum; use self::stdlib::str::FromStr; use self::stdlib::string::{String, ToString}; @@ -94,6 +94,7 @@ mod impl_convert; // Add, Sub, etc... mod impl_ops; mod impl_ops_add; +mod impl_ops_sub; // PartialEq mod impl_cmp; @@ -1172,172 +1173,6 @@ impl One for BigDecimal { } -impl Sub for BigDecimal { - type Output = BigDecimal; - - #[inline] - fn sub(self, rhs: BigDecimal) -> BigDecimal { - let mut lhs = self; - let scale = cmp::max(lhs.scale, rhs.scale); - - match lhs.scale.cmp(&rhs.scale) { - Ordering::Equal => { - lhs.int_val -= rhs.int_val; - lhs - } - Ordering::Less => lhs.take_and_scale(scale) - rhs, - Ordering::Greater => lhs - rhs.take_and_scale(scale), - } - } -} - -impl<'a> Sub<&'a BigDecimal> for BigDecimal { - type Output = BigDecimal; - - #[inline] - fn sub(self, rhs: &BigDecimal) -> BigDecimal { - let mut lhs = self; - let scale = cmp::max(lhs.scale, rhs.scale); - - match lhs.scale.cmp(&rhs.scale) { - Ordering::Equal => { - lhs.int_val -= &rhs.int_val; - lhs - } - Ordering::Less => lhs.take_and_scale(rhs.scale) - rhs, - Ordering::Greater => lhs - rhs.with_scale(scale), - } - } -} - -impl<'a> Sub for &'a BigDecimal { - type Output = BigDecimal; - - #[inline] - fn sub(self, rhs: BigDecimal) -> BigDecimal { - -(rhs - self) - } -} - -impl<'a, 'b> Sub<&'b BigDecimal> for &'a BigDecimal { - type Output = BigDecimal; - - #[inline] - fn sub(self, rhs: &BigDecimal) -> BigDecimal { - match self.scale.cmp(&rhs.scale) { - Ordering::Greater => { - let rhs = rhs.with_scale(self.scale); - self - rhs - } - Ordering::Less => self.with_scale(rhs.scale) - rhs, - Ordering::Equal => BigDecimal::new(&self.int_val - &rhs.int_val, self.scale), - } - } -} - -impl Sub for BigDecimal { - type Output = BigDecimal; - - #[inline] - fn sub(self, rhs: BigInt) -> BigDecimal { - let mut lhs = self; - - match lhs.scale.cmp(&0) { - Ordering::Equal => { - lhs.int_val -= rhs; - lhs - } - Ordering::Greater => { - lhs.int_val -= rhs * ten_to_the(lhs.scale as u64); - lhs - } - Ordering::Less => lhs.take_and_scale(0) - rhs, - } - } -} - -impl<'a> Sub<&'a BigInt> for BigDecimal { - type Output = BigDecimal; - - #[inline] - fn sub(self, rhs: &BigInt) -> BigDecimal { - let mut lhs = self; - - match lhs.scale.cmp(&0) { - Ordering::Equal => { - lhs.int_val -= rhs; - lhs - } - Ordering::Greater => { - lhs.int_val -= rhs * ten_to_the(lhs.scale as u64); - lhs - } - Ordering::Less => lhs.take_and_scale(0) - rhs, - } - } -} - -impl<'a> Sub for &'a BigDecimal { - type Output = BigDecimal; - - #[inline] - fn sub(self, rhs: BigInt) -> BigDecimal { - BigDecimal::new(rhs, 0) - self - } -} - -impl<'a, 'b> Sub<&'a BigInt> for &'b BigDecimal { - type Output = BigDecimal; - - #[inline] - fn sub(self, rhs: &BigInt) -> BigDecimal { - self.with_scale(0) - rhs - } -} - -forward_val_assignop!(impl SubAssign for BigDecimal, sub_assign); - -impl<'a> SubAssign<&'a BigDecimal> for BigDecimal { - #[inline] - fn sub_assign(&mut self, rhs: &BigDecimal) { - match self.scale.cmp(&rhs.scale) { - Ordering::Less => { - let lhs = self.with_scale(rhs.scale); - self.int_val = lhs.int_val - &rhs.int_val; - self.scale = rhs.scale; - } - Ordering::Greater => { - self.int_val -= rhs.with_scale(self.scale).int_val; - } - Ordering::Equal => { - self.int_val = &self.int_val - &rhs.int_val; - } - } - } -} - -impl SubAssign for BigDecimal { - #[inline(always)] - fn sub_assign(&mut self, rhs: BigInt) { - *self -= BigDecimal::new(rhs, 0) - } -} - -impl<'a> SubAssign<&'a BigInt> for BigDecimal { - #[inline(always)] - fn sub_assign(&mut self, rhs: &BigInt) { - match self.scale.cmp(&0) { - Ordering::Equal => SubAssign::sub_assign(&mut self.int_val, rhs), - Ordering::Greater => SubAssign::sub_assign(&mut self.int_val, rhs * ten_to_the(self.scale as u64)), - Ordering::Less => { - self.int_val *= ten_to_the((-self.scale) as u64); - SubAssign::sub_assign(&mut self.int_val, rhs); - self.scale = 0; - } - } - } -} - impl Mul for BigDecimal { type Output = BigDecimal; From 140a3c797da2deba5093c6c0715f972e4c616dfb Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Mon, 25 Sep 2023 21:57:04 -0400 Subject: [PATCH 25/77] Move subtraction tests into impl_ops_sub --- src/impl_ops_sub.rs | 76 +++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 26 ---------------- 2 files changed, 76 insertions(+), 26 deletions(-) diff --git a/src/impl_ops_sub.rs b/src/impl_ops_sub.rs index 10a460d..a6e2502 100644 --- a/src/impl_ops_sub.rs +++ b/src/impl_ops_sub.rs @@ -183,3 +183,79 @@ impl<'a> SubAssign<&'a BigInt> for BigDecimal { } } } + + +#[cfg(test)] +mod test { + use super::*; + use paste::paste; + + macro_rules! impl_case { + ($name:ident: $a:literal - $b:literal => $c:literal ) => { + #[test] + fn $name() { + let mut a: BigDecimal = $a.parse().unwrap(); + let b: BigDecimal = $b.parse().unwrap(); + let c: BigDecimal = $c.parse().unwrap(); + + assert_eq!(a.clone() - b.clone(), c); + + assert_eq!(a.clone() - &b, c); + assert_eq!(&a - b.clone(), c); + assert_eq!(&a - &b, c); + + a -= b; + assert_eq!(a, c); + } + }; + } + + impl_case!(case_1234en2_1234en3: "12.34" - "1.234" => "11.106"); + impl_case!(case_1234en2_n1234en3: "12.34" - "-1.234" => "13.574"); + impl_case!(case_1234e6_1234en6: "1234e6" - "1234e-6" => "1233999999.998766"); + + #[cfg(property_tests)] + mod prop { + use super::*; + use proptest::*; + use num_traits::FromPrimitive; + + proptest! { + #[test] + fn sub_refs_and_owners(f: f32, g: f32) { + // ignore non-normal numbers + prop_assume!(f.is_normal()); + prop_assume!(g.is_normal()); + + let a = BigDecimal::from_f32(f).unwrap(); + let b = BigDecimal::from_f32(g).unwrap(); + let own_plus_ref = a.clone() + &b; + let ref_plus_own = &a + b.clone(); + + let mut c = a.clone(); + c += &b; + + let mut d = a.clone(); + d += b; + + prop_assert_eq!(&own_plus_ref, &ref_plus_own); + prop_assert_eq!(&c, &ref_plus_own); + prop_assert_eq!(&d, &ref_plus_own); + } + + #[test] + fn subtraction_is_anticommunative(f: f32, g: f32) { + // ignore non-normal numbers + prop_assume!(f.is_normal()); + prop_assume!(g.is_normal()); + + let a = BigDecimal::from_f32(f).unwrap(); + let b = BigDecimal::from_f32(g).unwrap(); + let a_minus_b = &a - &b; + let b_minus_a = &b - &a; + + prop_assert_eq!(a_minus_b, -b_minus_a) + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index c5a0cbe..f1e6492 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2123,35 +2123,9 @@ mod bigdecimal_tests { assert!(BigDecimal::try_from(f64::NAN).is_err()); } - #[test] - fn test_sub() { - let vals = vec![ - ("12.34", "1.234", "11.106"), - ("12.34", "-1.234", "13.574"), - ("1234e6", "1234e-6", "1233999999.998766"), - ]; - - for &(x, y, z) in vals.iter() { - - let mut a = BigDecimal::from_str(x).unwrap(); - let b = BigDecimal::from_str(y).unwrap(); - let c = BigDecimal::from_str(z).unwrap(); - - assert_eq!(a.clone() - b.clone(), c); - - assert_eq!(a.clone() - &b, c); - assert_eq!(&a - b.clone(), c); - assert_eq!(&a - &b, c); - - a -= b; - assert_eq!(a, c); - } - } - /// Test multiplication of two bigdecimals #[test] fn test_mul() { - let vals = vec![ ("2", "1", "2"), ("12.34", "1.234", "15.22756"), From 486b600a9764c25488557d90cdda68c8cdc7e367 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Mon, 25 Sep 2023 01:33:38 -0400 Subject: [PATCH 26/77] Move impl Mul & MulAssign into impl_ops_mul.rs --- src/impl_ops_mul.rs | 222 ++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 208 +---------------------------------------- 2 files changed, 224 insertions(+), 206 deletions(-) create mode 100644 src/impl_ops_mul.rs diff --git a/src/impl_ops_mul.rs b/src/impl_ops_mul.rs new file mode 100644 index 0000000..b6259d4 --- /dev/null +++ b/src/impl_ops_mul.rs @@ -0,0 +1,222 @@ +//! Multiplication operator trait implementation +//! + +use crate::{ + Sign, + BigDecimal, + BigDecimalRef, +}; + +use crate::stdlib::{ + mem::swap, + ops::{Mul, MulAssign, AddAssign, Neg}, + cmp::Ordering, +}; + +use num_bigint::{BigInt, BigUint}; +use num_traits::{Zero, One}; +use crate::ten_to_the; + +impl Mul for BigDecimal { + type Output = BigDecimal; + + #[inline] + fn mul(mut self, rhs: BigDecimal) -> BigDecimal { + if self.is_one() { + rhs + } else if rhs.is_one() { + self + } else { + self.scale += rhs.scale; + self.int_val *= rhs.int_val; + self + } + } +} + +impl<'a> Mul<&'a BigDecimal> for BigDecimal { + type Output = BigDecimal; + + #[inline] + fn mul(mut self, rhs: &'a BigDecimal) -> BigDecimal { + if self.is_one() { + self.scale = rhs.scale; + self.int_val.set_zero(); + self.int_val.add_assign(&rhs.int_val); + self + } else if rhs.is_one() { + self + } else { + self.scale += rhs.scale; + MulAssign::mul_assign(&mut self.int_val, &rhs.int_val); + self + } + } +} + +impl<'a> Mul for &'a BigDecimal { + type Output = BigDecimal; + + #[inline] + fn mul(self, rhs: BigDecimal) -> BigDecimal { + rhs * self + } +} + +impl<'a, 'b> Mul<&'b BigDecimal> for &'a BigDecimal { + type Output = BigDecimal; + + #[inline] + fn mul(self, rhs: &BigDecimal) -> BigDecimal { + if self.is_one() { + rhs.normalized() + } else if rhs.is_one() { + self.normalized() + } else { + let scale = self.scale + rhs.scale; + BigDecimal::new(&self.int_val * &rhs.int_val, scale) + } + } +} + +impl Mul for BigDecimal { + type Output = BigDecimal; + + #[inline] + fn mul(mut self, rhs: BigInt) -> BigDecimal { + self.int_val *= rhs; + self + } +} + +impl<'a> Mul<&'a BigInt> for BigDecimal { + type Output = BigDecimal; + + #[inline] + fn mul(mut self, rhs: &BigInt) -> BigDecimal { + self.int_val *= rhs; + self + } +} + +impl<'a> Mul for &'a BigDecimal { + type Output = BigDecimal; + + #[inline] + fn mul(self, mut rhs: BigInt) -> BigDecimal { + rhs *= &self.int_val; + BigDecimal::new(rhs, self.scale) + } +} + +impl<'a, 'b> Mul<&'a BigInt> for &'b BigDecimal { + type Output = BigDecimal; + + #[inline] + fn mul(self, rhs: &BigInt) -> BigDecimal { + if rhs.is_one() { + self.normalized() + } else if self.is_one() { + BigDecimal::new(rhs.clone(), 0) + } else { + let value = &self.int_val * rhs; + BigDecimal::new(value, self.scale) + } + } +} + +impl Mul for BigInt { + type Output = BigDecimal; + + #[inline] + fn mul(mut self, mut rhs: BigDecimal) -> BigDecimal { + if rhs.is_one() { + rhs.scale = 0; + swap(&mut rhs.int_val, &mut self); + } else if !self.is_one() { + rhs.int_val *= self; + } + rhs + } +} + +impl<'a> Mul for &'a BigInt { + type Output = BigDecimal; + + #[inline] + fn mul(self, mut rhs: BigDecimal) -> BigDecimal { + if self.is_one() { + rhs.normalized() + } else if rhs.is_one() { + rhs.int_val.set_zero(); + rhs.int_val += self; + rhs.scale = 0; + rhs + } else { + rhs.int_val *= self; + rhs + } + } +} + +impl<'a, 'b> Mul<&'a BigDecimal> for &'b BigInt { + type Output = BigDecimal; + + #[inline] + fn mul(self, rhs: &BigDecimal) -> BigDecimal { + if self.is_one() { + rhs.normalized() + } else if rhs.is_one() { + BigDecimal::new(self.clone(), 0) + } else { + let value = &rhs.int_val * self; + BigDecimal::new(value, rhs.scale) + } + } +} + +impl<'a> Mul<&'a BigDecimal> for BigInt { + type Output = BigDecimal; + + #[inline] + fn mul(mut self, rhs: &BigDecimal) -> BigDecimal { + if self.is_one() { + rhs.normalized() + } else if rhs.is_one() { + BigDecimal::new(self, 0) + } else { + self *= &rhs.int_val; + BigDecimal::new(self, rhs.scale) + } + } +} + +forward_val_assignop!(impl MulAssign for BigDecimal, mul_assign); + +impl<'a> MulAssign<&'a BigDecimal> for BigDecimal { + #[inline] + fn mul_assign(&mut self, rhs: &BigDecimal) { + if rhs.is_one() { + return; + } + self.scale += rhs.scale; + self.int_val = &self.int_val * &rhs.int_val; + } +} + +impl<'a> MulAssign<&'a BigInt> for BigDecimal { + #[inline] + fn mul_assign(&mut self, rhs: &BigInt) { + if rhs.is_one() { + return; + } + self.int_val *= rhs; + } +} + +impl MulAssign for BigDecimal { + #[inline] + fn mul_assign(&mut self, rhs: BigInt) { + *self *= &rhs + } +} diff --git a/src/lib.rs b/src/lib.rs index f1e6492..ba12a46 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -67,7 +67,7 @@ use self::stdlib::convert::TryFrom; use self::stdlib::default::Default; use self::stdlib::hash::{Hash, Hasher}; use self::stdlib::num::{ParseFloatError, ParseIntError}; -use self::stdlib::ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Rem, Sub}; +use self::stdlib::ops::{Add, AddAssign, Div, Mul, Neg, Rem, Sub}; use self::stdlib::iter::Sum; use self::stdlib::str::FromStr; use self::stdlib::string::{String, ToString}; @@ -95,6 +95,7 @@ mod impl_convert; mod impl_ops; mod impl_ops_add; mod impl_ops_sub; +mod impl_ops_mul; // PartialEq mod impl_cmp; @@ -1173,211 +1174,6 @@ impl One for BigDecimal { } -impl Mul for BigDecimal { - type Output = BigDecimal; - - #[inline] - fn mul(mut self, rhs: BigDecimal) -> BigDecimal { - if self.is_one() { - rhs - } else if rhs.is_one() { - self - } else { - self.scale += rhs.scale; - self.int_val *= rhs.int_val; - self - } - } -} - -impl<'a> Mul<&'a BigDecimal> for BigDecimal { - type Output = BigDecimal; - - #[inline] - fn mul(mut self, rhs: &'a BigDecimal) -> BigDecimal { - if self.is_one() { - self.scale = rhs.scale; - self.int_val.set_zero(); - self.int_val.add_assign(&rhs.int_val); - self - } else if rhs.is_one() { - self - } else { - self.scale += rhs.scale; - MulAssign::mul_assign(&mut self.int_val, &rhs.int_val); - self - } - } -} - -impl<'a> Mul for &'a BigDecimal { - type Output = BigDecimal; - - #[inline] - fn mul(self, rhs: BigDecimal) -> BigDecimal { - rhs * self - } -} - -impl<'a, 'b> Mul<&'b BigDecimal> for &'a BigDecimal { - type Output = BigDecimal; - - #[inline] - fn mul(self, rhs: &BigDecimal) -> BigDecimal { - if self.is_one() { - rhs.normalized() - } else if rhs.is_one() { - self.normalized() - } else { - let scale = self.scale + rhs.scale; - BigDecimal::new(&self.int_val * &rhs.int_val, scale) - } - } -} - -impl Mul for BigDecimal { - type Output = BigDecimal; - - #[inline] - fn mul(mut self, rhs: BigInt) -> BigDecimal { - self.int_val *= rhs; - self - } -} - -impl<'a> Mul<&'a BigInt> for BigDecimal { - type Output = BigDecimal; - - #[inline] - fn mul(mut self, rhs: &BigInt) -> BigDecimal { - self.int_val *= rhs; - self - } -} - -impl<'a> Mul for &'a BigDecimal { - type Output = BigDecimal; - - #[inline] - fn mul(self, mut rhs: BigInt) -> BigDecimal { - rhs *= &self.int_val; - BigDecimal::new(rhs, self.scale) - } -} - -impl<'a, 'b> Mul<&'a BigInt> for &'b BigDecimal { - type Output = BigDecimal; - - #[inline] - fn mul(self, rhs: &BigInt) -> BigDecimal { - if rhs.is_one() { - self.normalized() - } else if self.is_one() { - BigDecimal::new(rhs.clone(), 0) - } else { - let value = &self.int_val * rhs; - BigDecimal::new(value, self.scale) - } - } -} - -impl Mul for BigInt { - type Output = BigDecimal; - - #[inline] - fn mul(mut self, mut rhs: BigDecimal) -> BigDecimal { - if rhs.is_one() { - rhs.scale = 0; - stdlib::mem::swap(&mut rhs.int_val, &mut self); - } else if !self.is_one() { - rhs.int_val *= self; - } - rhs - } -} - -impl<'a> Mul for &'a BigInt { - type Output = BigDecimal; - - #[inline] - fn mul(self, mut rhs: BigDecimal) -> BigDecimal { - if self.is_one() { - rhs.normalized() - } else if rhs.is_one() { - rhs.int_val.set_zero(); - rhs.int_val += self; - rhs.scale = 0; - rhs - } else { - rhs.int_val *= self; - rhs - } - } -} - -impl<'a, 'b> Mul<&'a BigDecimal> for &'b BigInt { - type Output = BigDecimal; - - #[inline] - fn mul(self, rhs: &BigDecimal) -> BigDecimal { - if self.is_one() { - rhs.normalized() - } else if rhs.is_one() { - BigDecimal::new(self.clone(), 0) - } else { - let value = &rhs.int_val * self; - BigDecimal::new(value, rhs.scale) - } - } -} - -impl<'a> Mul<&'a BigDecimal> for BigInt { - type Output = BigDecimal; - - #[inline] - fn mul(mut self, rhs: &BigDecimal) -> BigDecimal { - if self.is_one() { - rhs.normalized() - } else if rhs.is_one() { - BigDecimal::new(self, 0) - } else { - self *= &rhs.int_val; - BigDecimal::new(self, rhs.scale) - } - } -} - -forward_val_assignop!(impl MulAssign for BigDecimal, mul_assign); - -impl<'a> MulAssign<&'a BigDecimal> for BigDecimal { - #[inline] - fn mul_assign(&mut self, rhs: &BigDecimal) { - if rhs.is_one() { - return; - } - self.scale += rhs.scale; - self.int_val = &self.int_val * &rhs.int_val; - } -} - -impl<'a> MulAssign<&'a BigInt> for BigDecimal { - #[inline] - fn mul_assign(&mut self, rhs: &BigInt) { - if rhs.is_one() { - return; - } - self.int_val *= rhs; - } -} - -impl MulAssign for BigDecimal { - #[inline] - fn mul_assign(&mut self, rhs: BigInt) { - *self *= &rhs - } -} - - #[inline(always)] fn impl_division(mut num: BigInt, den: &BigInt, mut scale: i64, max_precision: u64) -> BigDecimal { // quick zero check From 7bd7a28be90053ec3049f45c76f72cd426d445b6 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Mon, 25 Sep 2023 01:37:06 -0400 Subject: [PATCH 27/77] Move multiplication tests into impl_ops_mul --- src/impl_ops_mul.rs | 74 +++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 63 -------------------------------------- 2 files changed, 74 insertions(+), 63 deletions(-) diff --git a/src/impl_ops_mul.rs b/src/impl_ops_mul.rs index b6259d4..f13ec58 100644 --- a/src/impl_ops_mul.rs +++ b/src/impl_ops_mul.rs @@ -220,3 +220,77 @@ impl MulAssign for BigDecimal { *self *= &rhs } } + + +#[cfg(test)] +#[allow(non_snake_case)] +mod bigdecimal_tests { + use crate::{stdlib, BigDecimal, ToString, FromStr, TryFrom}; + use num_traits::{ToPrimitive, FromPrimitive, Signed, Zero, One}; + use num_bigint; + use paste::paste; + + /// Test multiplication of two bigdecimals + #[test] + fn test_mul() { + + let vals = vec![ + ("2", "1", "2"), + ("12.34", "1.234", "15.22756"), + ("2e1", "1", "20"), + ("3", ".333333", "0.999999"), + ("2389472934723", "209481029831", "500549251119075878721813"), + ("1e-450", "1e500", ".1e51"), + ("-995052931372975485719.533153137", "4.523087321", "-4500711297616988541501.836966993116075977"), + ("995052931372975485719.533153137", "-4.523087321", "-4500711297616988541501.836966993116075977"), + ("-8.37664968", "-1.9086963714056968482094712882596748", "15.988480848752691653730876239769592670324064"), + ("-8.37664968", "0", "0"), + ]; + + for &(x, y, z) in vals.iter() { + + let mut a = BigDecimal::from_str(x).unwrap(); + let b = BigDecimal::from_str(y).unwrap(); + let c = BigDecimal::from_str(z).unwrap(); + + assert_eq!(a.clone() * b.clone(), c); + assert_eq!(a.clone() * &b, c); + assert_eq!(&a * b.clone(), c); + assert_eq!(&a * &b, c); + + a *= b; + assert_eq!(a, c); + } + } + + /// Test multiplication between big decimal and big integer + #[test] + fn test_mul_bigint() { + let vals = vec![ + ("2", "1", "2"), + ("8.561", "10", "85.61"), + ("1.0000", "638655273892892437", "638655273892892437"), + ("10000", "638655273892892437", "6386552738928924370000"), + (".0005", "368408638655273892892437473", "184204319327636946446218.7365"), + ("9e-1", "368408638655273892892437473", "331567774789746503603193725.7"), + ("-1.175470587012343730098", "577575785", "-678923347.038065234601180476930"), + ("-1.175470587012343730098", "-76527768352678", "89956140788267.069799533723307502444"), + ("-1.175470587012343730098", "0", "0"), + ]; + + for &(x, y, z) in vals.iter() { + let a = BigDecimal::from_str(x).unwrap(); + let b = num_bigint::BigInt::from_str(y).unwrap(); + let c = BigDecimal::from_str(z).unwrap(); + + assert_eq!(a.clone() * b.clone(), c); + assert_eq!(b.clone() * a.clone(), c); + assert_eq!(a.clone() * &b, c); + assert_eq!(b.clone() * &a, c); + assert_eq!(&a * b.clone(), c); + assert_eq!(&b * a.clone(), c); + assert_eq!(&a * &b, c); + assert_eq!(&b * &a, c); + } + } +} diff --git a/src/lib.rs b/src/lib.rs index ba12a46..aa3043f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1919,69 +1919,6 @@ mod bigdecimal_tests { assert!(BigDecimal::try_from(f64::NAN).is_err()); } - /// Test multiplication of two bigdecimals - #[test] - fn test_mul() { - let vals = vec![ - ("2", "1", "2"), - ("12.34", "1.234", "15.22756"), - ("2e1", "1", "20"), - ("3", ".333333", "0.999999"), - ("2389472934723", "209481029831", "500549251119075878721813"), - ("1e-450", "1e500", ".1e51"), - ("-995052931372975485719.533153137", "4.523087321", "-4500711297616988541501.836966993116075977"), - ("995052931372975485719.533153137", "-4.523087321", "-4500711297616988541501.836966993116075977"), - ("-8.37664968", "-1.9086963714056968482094712882596748", "15.988480848752691653730876239769592670324064"), - ("-8.37664968", "0", "0"), - ]; - - for &(x, y, z) in vals.iter() { - - let mut a = BigDecimal::from_str(x).unwrap(); - let b = BigDecimal::from_str(y).unwrap(); - let c = BigDecimal::from_str(z).unwrap(); - - assert_eq!(a.clone() * b.clone(), c); - assert_eq!(a.clone() * &b, c); - assert_eq!(&a * b.clone(), c); - assert_eq!(&a * &b, c); - - a *= b; - assert_eq!(a, c); - } - } - - /// Test multiplication between big decimal and big integer - #[test] - fn test_mul_bigint() { - let vals = vec![ - ("2", "1", "2"), - ("8.561", "10", "85.61"), - ("1.0000", "638655273892892437", "638655273892892437"), - ("10000", "638655273892892437", "6386552738928924370000"), - (".0005", "368408638655273892892437473", "184204319327636946446218.7365"), - ("9e-1", "368408638655273892892437473", "331567774789746503603193725.7"), - ("-1.175470587012343730098", "577575785", "-678923347.038065234601180476930"), - ("-1.175470587012343730098", "-76527768352678", "89956140788267.069799533723307502444"), - ("-1.175470587012343730098", "0", "0"), - ]; - - for &(x, y, z) in vals.iter() { - let a = BigDecimal::from_str(x).unwrap(); - let b = num_bigint::BigInt::from_str(y).unwrap(); - let c = BigDecimal::from_str(z).unwrap(); - - assert_eq!(a.clone() * b.clone(), c); - assert_eq!(b.clone() * a.clone(), c); - assert_eq!(a.clone() * &b, c); - assert_eq!(b.clone() * &a, c); - assert_eq!(&a * b.clone(), c); - assert_eq!(&b * a.clone(), c); - assert_eq!(&a * &b, c); - assert_eq!(&b * &a, c); - } - } - #[test] fn test_div() { let vals = vec![ From e550656faa4a7290c4ac60a39158a8ea0824ce29 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Tue, 26 Sep 2023 23:50:18 -0400 Subject: [PATCH 28/77] Move impl Div into impl_ops_div.rs --- src/impl_ops_div.rs | 88 +++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 88 ++------------------------------------------- 2 files changed, 91 insertions(+), 85 deletions(-) create mode 100644 src/impl_ops_div.rs diff --git a/src/impl_ops_div.rs b/src/impl_ops_div.rs new file mode 100644 index 0000000..2a2cee4 --- /dev/null +++ b/src/impl_ops_div.rs @@ -0,0 +1,88 @@ +//! Implement division + +use super::*; + +impl Div for BigDecimal { + type Output = BigDecimal; + #[inline] + fn div(self, other: BigDecimal) -> BigDecimal { + if other.is_zero() { + panic!("Division by zero"); + } + if self.is_zero() || other.is_one() { + return self; + } + + let scale = self.scale - other.scale; + + if self.int_val == other.int_val { + return BigDecimal { + int_val: 1.into(), + scale: scale, + }; + } + + let max_precision = DEFAULT_PRECISION; + + return impl_division(self.int_val, &other.int_val, scale, max_precision); + } +} + +impl<'a> Div<&'a BigDecimal> for BigDecimal { + type Output = BigDecimal; + #[inline] + fn div(self, other: &'a BigDecimal) -> BigDecimal { + if other.is_zero() { + panic!("Division by zero"); + } + if self.is_zero() || other.is_one() { + return self; + } + + let scale = self.scale - other.scale; + + if self.int_val == other.int_val { + return BigDecimal { + int_val: 1.into(), + scale: scale, + }; + } + + let max_precision = DEFAULT_PRECISION; + + return impl_division(self.int_val, &other.int_val, scale, max_precision); + } +} + +forward_ref_val_binop!(impl Div for BigDecimal, div); + +impl<'a, 'b> Div<&'b BigDecimal> for &'a BigDecimal { + type Output = BigDecimal; + + #[inline] + fn div(self, other: &BigDecimal) -> BigDecimal { + if other.is_zero() { + panic!("Division by zero"); + } + // TODO: Fix setting scale + if self.is_zero() || other.is_one() { + return self.clone(); + } + + let scale = self.scale - other.scale; + + let num_int = &self.int_val; + let den_int = &other.int_val; + + if num_int == den_int { + return BigDecimal { + int_val: 1.into(), + scale: scale, + }; + } + + let max_precision = DEFAULT_PRECISION; + + return impl_division(num_int.clone(), den_int, scale, max_precision); + } +} diff --git a/src/lib.rs b/src/lib.rs index aa3043f..ddf25a5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -96,6 +96,7 @@ mod impl_ops; mod impl_ops_add; mod impl_ops_sub; mod impl_ops_mul; +mod impl_ops_div; // PartialEq mod impl_cmp; @@ -1174,7 +1175,8 @@ impl One for BigDecimal { } -#[inline(always)] + + fn impl_division(mut num: BigInt, den: &BigInt, mut scale: i64, max_precision: u64) -> BigDecimal { // quick zero check if num.is_zero() { @@ -1230,90 +1232,6 @@ fn impl_division(mut num: BigInt, den: &BigInt, mut scale: i64, max_precision: u return result; } -impl Div for BigDecimal { - type Output = BigDecimal; - #[inline] - fn div(self, other: BigDecimal) -> BigDecimal { - if other.is_zero() { - panic!("Division by zero"); - } - if self.is_zero() || other.is_one() { - return self; - } - - let scale = self.scale - other.scale; - - if self.int_val == other.int_val { - return BigDecimal { - int_val: 1.into(), - scale: scale, - }; - } - - let max_precision = DEFAULT_PRECISION; - - return impl_division(self.int_val, &other.int_val, scale, max_precision); - } -} - -impl<'a> Div<&'a BigDecimal> for BigDecimal { - type Output = BigDecimal; - #[inline] - fn div(self, other: &'a BigDecimal) -> BigDecimal { - if other.is_zero() { - panic!("Division by zero"); - } - if self.is_zero() || other.is_one() { - return self; - } - - let scale = self.scale - other.scale; - - if self.int_val == other.int_val { - return BigDecimal { - int_val: 1.into(), - scale: scale, - }; - } - - let max_precision = DEFAULT_PRECISION; - - return impl_division(self.int_val, &other.int_val, scale, max_precision); - } -} - -forward_ref_val_binop!(impl Div for BigDecimal, div); - -impl<'a, 'b> Div<&'b BigDecimal> for &'a BigDecimal { - type Output = BigDecimal; - - #[inline] - fn div(self, other: &BigDecimal) -> BigDecimal { - if other.is_zero() { - panic!("Division by zero"); - } - // TODO: Fix setting scale - if self.is_zero() || other.is_one() { - return self.clone(); - } - - let scale = self.scale - other.scale; - - let num_int = &self.int_val; - let den_int = &other.int_val; - - if num_int == den_int { - return BigDecimal { - int_val: 1.into(), - scale: scale, - }; - } - - let max_precision = DEFAULT_PRECISION; - - return impl_division(num_int.clone(), den_int, scale, max_precision); - } -} impl Rem for BigDecimal { type Output = BigDecimal; From c9c860a5d51da62d3dfe91f8a260c82399fd4d95 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Wed, 27 Sep 2023 19:48:30 -0400 Subject: [PATCH 29/77] Move division tests to impl_ops_div.rs --- src/impl_ops_div.rs | 68 +++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 62 ----------------------------------------- 2 files changed, 68 insertions(+), 62 deletions(-) diff --git a/src/impl_ops_div.rs b/src/impl_ops_div.rs index 2a2cee4..1a756bd 100644 --- a/src/impl_ops_div.rs +++ b/src/impl_ops_div.rs @@ -86,3 +86,71 @@ impl<'a, 'b> Div<&'b BigDecimal> for &'a BigDecimal { return impl_division(num_int.clone(), den_int, scale, max_precision); } } + + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_div() { + let vals = vec![ + ("0", "1", "0"), + ("0", "10", "0"), + ("2", "1", "2"), + ("2e1", "1", "2e1"), + ("10", "10", "1"), + ("100", "10.0", "1e1"), + ("20.0", "200", ".1"), + ("4", "2", "2.0"), + ("15", "3", "5.0"), + ("1", "2", "0.5"), + ("1", "2e-2", "5e1"), + ("1", "0.2", "5"), + ("1.0", "0.02", "50"), + ("1", "0.020", "5e1"), + ("5.0", "4.00", "1.25"), + ("5.0", "4.000", "1.25"), + ("5", "4.000", "1.25"), + ("5", "4", "125e-2"), + ("100", "5", "20"), + ("-50", "5", "-10"), + ("200", "-5", "-40."), + ("1", "3", ".3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333"), + ("-2", "-3", ".6666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666667"), + ("-12.34", "1.233", "-10.00811030008110300081103000811030008110300081103000811030008110300081103000811030008110300081103001"), + ("125348", "352.2283", "355.8714617763535752237966114591019517738921035021887792661748076460636467881768727839301952739175132"), + ]; + + for &(x, y, z) in vals.iter() { + + let a = BigDecimal::from_str(x).unwrap(); + let b = BigDecimal::from_str(y).unwrap(); + let c = BigDecimal::from_str(z).unwrap(); + + assert_eq!(a.clone() / b.clone(), c); + assert_eq!(a.clone() / &b, c); + assert_eq!(&a / b.clone(), c); + assert_eq!(&a / &b, c); + // assert_eq!(q.scale, c.scale); + + // let mut q = a; + // q /= b; + // assert_eq!(q, c); + } + } + + #[test] + #[should_panic(expected = "Division by zero")] + fn test_division_by_zero_panics() { + let x = BigDecimal::from_str("3.14").unwrap(); + let _r = x / 0; + } + + #[test] + #[should_panic(expected = "Division by zero")] + fn test_division_by_zero_panics_v2() { + let x = BigDecimal::from_str("3.14").unwrap(); + let _r = x / BigDecimal::zero(); + } +} diff --git a/src/lib.rs b/src/lib.rs index ddf25a5..e0e985c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1837,68 +1837,6 @@ mod bigdecimal_tests { assert!(BigDecimal::try_from(f64::NAN).is_err()); } - #[test] - fn test_div() { - let vals = vec![ - ("0", "1", "0"), - ("0", "10", "0"), - ("2", "1", "2"), - ("2e1", "1", "2e1"), - ("10", "10", "1"), - ("100", "10.0", "1e1"), - ("20.0", "200", ".1"), - ("4", "2", "2.0"), - ("15", "3", "5.0"), - ("1", "2", "0.5"), - ("1", "2e-2", "5e1"), - ("1", "0.2", "5"), - ("1.0", "0.02", "50"), - ("1", "0.020", "5e1"), - ("5.0", "4.00", "1.25"), - ("5.0", "4.000", "1.25"), - ("5", "4.000", "1.25"), - ("5", "4", "125e-2"), - ("100", "5", "20"), - ("-50", "5", "-10"), - ("200", "-5", "-40."), - ("1", "3", ".3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333"), - ("-2", "-3", ".6666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666667"), - ("-12.34", "1.233", "-10.00811030008110300081103000811030008110300081103000811030008110300081103000811030008110300081103001"), - ("125348", "352.2283", "355.8714617763535752237966114591019517738921035021887792661748076460636467881768727839301952739175132"), - ]; - - for &(x, y, z) in vals.iter() { - - let a = BigDecimal::from_str(x).unwrap(); - let b = BigDecimal::from_str(y).unwrap(); - let c = BigDecimal::from_str(z).unwrap(); - - assert_eq!(a.clone() / b.clone(), c); - assert_eq!(a.clone() / &b, c); - assert_eq!(&a / b.clone(), c); - assert_eq!(&a / &b, c); - // assert_eq!(q.scale, c.scale); - - // let mut q = a; - // q /= b; - // assert_eq!(q, c); - } - } - - #[test] - #[should_panic(expected = "Division by zero")] - fn test_division_by_zero_panics() { - let x = BigDecimal::from_str("3.14").unwrap(); - let _r = x / 0; - } - - #[test] - #[should_panic(expected = "Division by zero")] - fn test_division_by_zero_panics_v2() { - let x = BigDecimal::from_str("3.14").unwrap(); - let _r = x / BigDecimal::zero(); - } - #[test] fn test_rem() { let vals = vec![ From 135912c58aae47af079afbcecc9bb0e79896600c Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Wed, 27 Sep 2023 19:50:30 -0400 Subject: [PATCH 30/77] Move impl Rem into impl_ops_rem.rs --- src/impl_ops_rem.rs | 78 +++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 76 +------------------------------------------ 2 files changed, 79 insertions(+), 75 deletions(-) create mode 100644 src/impl_ops_rem.rs diff --git a/src/impl_ops_rem.rs b/src/impl_ops_rem.rs new file mode 100644 index 0000000..080d1b8 --- /dev/null +++ b/src/impl_ops_rem.rs @@ -0,0 +1,78 @@ +//! Remainder implementations + +use super::*; + +impl Rem for BigDecimal { + type Output = BigDecimal; + + #[inline] + fn rem(self, other: BigDecimal) -> BigDecimal { + 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; + + BigDecimal::new(num % den, scale) + } +} + +impl<'a> Rem<&'a BigDecimal> for BigDecimal { + type Output = BigDecimal; + + #[inline] + fn rem(self, other: &BigDecimal) -> BigDecimal { + let scale = cmp::max(self.scale, other.scale); + let num = self.take_and_scale(scale).int_val; + let den = &other.int_val; + + let result = if scale == other.scale { + num % den + } else { + num % (den * ten_to_the((scale - other.scale) as u64)) + }; + BigDecimal::new(result, scale) + } +} +impl<'a> Rem for &'a BigDecimal { + type Output = BigDecimal; + + #[inline] + fn rem(self, other: BigDecimal) -> BigDecimal { + let scale = cmp::max(self.scale, other.scale); + let num = &self.int_val; + let den = other.take_and_scale(scale).int_val; + + let result = if scale == self.scale { + num % den + } else { + let scaled_num = num * ten_to_the((scale - self.scale) as u64); + scaled_num % den + }; + + BigDecimal::new(result, scale) + } +} + +impl<'a, 'b> Rem<&'b BigDecimal> for &'a BigDecimal { + type Output = BigDecimal; + + #[inline] + fn rem(self, other: &BigDecimal) -> BigDecimal { + let scale = cmp::max(self.scale, other.scale); + let num = &self.int_val; + let den = &other.int_val; + + let result = match self.scale.cmp(&other.scale) { + Ordering::Equal => num % den, + Ordering::Less => { + let scaled_num = num * ten_to_the((scale - self.scale) as u64); + scaled_num % den + } + Ordering::Greater => { + let scaled_den = den * ten_to_the((scale - other.scale) as u64); + num % scaled_den + } + }; + BigDecimal::new(result, scale) + } +} diff --git a/src/lib.rs b/src/lib.rs index e0e985c..327cc03 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,6 +97,7 @@ mod impl_ops_add; mod impl_ops_sub; mod impl_ops_mul; mod impl_ops_div; +mod impl_ops_rem; // PartialEq mod impl_cmp; @@ -1233,81 +1234,6 @@ fn impl_division(mut num: BigInt, den: &BigInt, mut scale: i64, max_precision: u } -impl Rem for BigDecimal { - type Output = BigDecimal; - - #[inline] - fn rem(self, other: BigDecimal) -> BigDecimal { - 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; - - BigDecimal::new(num % den, scale) - } -} - -impl<'a> Rem<&'a BigDecimal> for BigDecimal { - type Output = BigDecimal; - - #[inline] - fn rem(self, other: &BigDecimal) -> BigDecimal { - let scale = cmp::max(self.scale, other.scale); - let num = self.take_and_scale(scale).int_val; - let den = &other.int_val; - - let result = if scale == other.scale { - num % den - } else { - num % (den * ten_to_the((scale - other.scale) as u64)) - }; - BigDecimal::new(result, scale) - } -} -impl<'a> Rem for &'a BigDecimal { - type Output = BigDecimal; - - #[inline] - fn rem(self, other: BigDecimal) -> BigDecimal { - let scale = cmp::max(self.scale, other.scale); - let num = &self.int_val; - let den = other.take_and_scale(scale).int_val; - - let result = if scale == self.scale { - num % den - } else { - let scaled_num = num * ten_to_the((scale - self.scale) as u64); - scaled_num % den - }; - - BigDecimal::new(result, scale) - } -} - -impl<'a, 'b> Rem<&'b BigDecimal> for &'a BigDecimal { - type Output = BigDecimal; - - #[inline] - fn rem(self, other: &BigDecimal) -> BigDecimal { - let scale = cmp::max(self.scale, other.scale); - let num = &self.int_val; - let den = &other.int_val; - - let result = match self.scale.cmp(&other.scale) { - Ordering::Equal => num % den, - Ordering::Less => { - let scaled_num = num * ten_to_the((scale - self.scale) as u64); - scaled_num % den - } - Ordering::Greater => { - let scaled_den = den * ten_to_the((scale - other.scale) as u64); - num % scaled_den - } - }; - BigDecimal::new(result, scale) - } -} - impl Signed for BigDecimal { #[inline] From b1c02acb5fce7ab05d5a16413e17ceedd1f7e2f3 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Wed, 27 Sep 2023 19:48:48 -0400 Subject: [PATCH 31/77] Move rem tests to impl_ops_rem.rs --- src/impl_ops_rem.rs | 78 +++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 49 ---------------------------- 2 files changed, 78 insertions(+), 49 deletions(-) diff --git a/src/impl_ops_rem.rs b/src/impl_ops_rem.rs index 080d1b8..4a4e3a4 100644 --- a/src/impl_ops_rem.rs +++ b/src/impl_ops_rem.rs @@ -33,6 +33,7 @@ impl<'a> Rem<&'a BigDecimal> for BigDecimal { BigDecimal::new(result, scale) } } + impl<'a> Rem for &'a BigDecimal { type Output = BigDecimal; @@ -76,3 +77,80 @@ impl<'a, 'b> Rem<&'b BigDecimal> for &'a BigDecimal { BigDecimal::new(result, scale) } } + + +#[cfg(test)] +mod test { + use super::*; + use paste::paste; + + macro_rules! impl_case { + ($a:literal % $b:literal => $c:literal ) => { + paste! { + impl_case!([< case_ $a _ $b >]: $a % $b => $c); + } + }; + ($name:ident: $a:literal % $b:literal => $c:literal ) => { + #[test] + fn $name() { + let mut a: BigDecimal = $a.parse().unwrap(); + let b: BigDecimal = $b.parse().unwrap(); + let c: BigDecimal = $c.parse().unwrap(); + + assert_eq!(a.clone() % b.clone(), c); + + assert_eq!(a.clone() % &b, c); + assert_eq!(&a % b.clone(), c); + assert_eq!(&a % &b, c); + } + }; + } + + impl_case!("100" % "5" => "0"); + impl_case!("2e1" % "1" => "0"); + impl_case!("2" % "1" => "0"); + impl_case!("1" % "3" => "1"); + impl_case!("1" % "5e-1" => "0"); + impl_case!("15e-1" % "1" => "0.5"); + impl_case!("1" % "3e-2" => "1e-2"); + impl_case!("10" % "3e-3" => "0.001"); + impl_case!("3" % "2" => "1"); + impl_case!("1234e-2" % "1233e-3" => "0.01"); + + impl_case!(case_neg3_2: "-3" % "2" => "-1"); + impl_case!(case_3_neg2: "3" % "-2" => "1"); + impl_case!(case_neg3_neg2: "3" % "-2" => "1"); + + impl_case!(case_neg95eneg1_515eneg2: "-9.5" % "5.15" => "-4.35"); + + + #[cfg(property_tests)] + mod prop { + use super::*; + use proptest::*; + use num_traits::FromPrimitive; + + proptest! { + #[test] + fn quotient_and_remainder(f: f32, g: f32) { + // ignore non-normal numbers + prop_assume!(f.is_normal()); + prop_assume!(g.is_normal()); + prop_assume!(!g.is_zero()); + + let (f, g) = if f.abs() > g.abs() { + (f, g) + } else { + (g, f) + }; + + let a = BigDecimal::from_f32(f).unwrap(); + let b = BigDecimal::from_f32(g).unwrap(); + + let r = &a % &b; + let q = (&a / &b).with_scale(0); + assert_eq!(a, q * b + r); + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 327cc03..a03ccb9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1763,55 +1763,6 @@ mod bigdecimal_tests { assert!(BigDecimal::try_from(f64::NAN).is_err()); } - #[test] - fn test_rem() { - let vals = vec![ - ("100", "5", "0"), - ("2e1", "1", "0"), - ("2", "1", "0"), - ("1", "3", "1"), - ("1", "0.5", "0"), - ("1.5", "1", "0.5"), - ("1", "3e-2", "1e-2"), - ("10", "0.003", "0.001"), - ("3", "2", "1"), - ("-3", "2", "-1"), - ("3", "-2", "1"), - ("-3", "-2", "-1"), - ("12.34", "1.233", "0.01"), - ]; - for &(x, y, z) in vals.iter() { - let a = BigDecimal::from_str(x).unwrap(); - let b = BigDecimal::from_str(y).unwrap(); - let c = BigDecimal::from_str(z).unwrap(); - - let rem = &a % &b; - assert_eq!(rem, c, "{} [&{} % &{}] == {}", rem, a, b, c); - - let rem = a.clone() % &b; - assert_eq!(rem, c, "{} [{} % &{}] == {}", rem, a, b, c); - - let rem = &a % b.clone(); - assert_eq!(rem, c, "{} [&{} % {}] == {}", rem, a, b, c); - - let rem = a.clone() % b.clone(); - assert_eq!(rem, c, "{} [{} % {}] == {}", rem, a, b, c); - } - let vals = vec![ - (("100", -2), ("50", -1), "0"), - (("100", 0), ("50", -1), "0"), - (("100", -2), ("30", 0), "10"), - (("100", 0), ("30", -1), "10"), - ]; - for &((x, xs), (y, ys), z) in vals.iter() { - let a = BigDecimal::from_str(x).unwrap().with_scale(xs); - let b = BigDecimal::from_str(y).unwrap().with_scale(ys); - let c = BigDecimal::from_str(z).unwrap(); - let rem = &a % &b; - assert_eq!(rem, c, "{} [{} % {}] == {}", rem, a, b, c); - } - } - #[test] fn test_equal() { let vals = vec![ From 97fe9706d078a55c5b1d107dcb47ed560b21602d Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Thu, 28 Sep 2023 20:08:17 -0400 Subject: [PATCH 32/77] Clean 'use' statements --- src/impl_ops.rs | 17 +---------------- src/impl_ops_add.rs | 15 ++------------- src/impl_ops_mul.rs | 18 +++--------------- src/impl_ops_rem.rs | 1 + src/impl_ops_sub.rs | 17 ++--------------- src/lib.rs | 14 ++++++++++---- 6 files changed, 19 insertions(+), 63 deletions(-) diff --git a/src/impl_ops.rs b/src/impl_ops.rs index 8a02e36..829f631 100644 --- a/src/impl_ops.rs +++ b/src/impl_ops.rs @@ -1,21 +1,6 @@ //! Implement math operations: Add,Sub, etc -use crate::{ - BigDecimal, - BigDecimalRef, -}; -use crate::stdlib::ops::{ - Add, AddAssign, - Sub, SubAssign, - Mul, MulAssign, - Div, DivAssign, - Neg, -}; - - -use crate::stdlib::convert::TryFrom; - -use num_traits::{Zero, One}; +use crate::*; macro_rules! impl_add_for_primitive { diff --git a/src/impl_ops_add.rs b/src/impl_ops_add.rs index 0245311..6928139 100644 --- a/src/impl_ops_add.rs +++ b/src/impl_ops_add.rs @@ -1,19 +1,8 @@ //! Addition operator trait implementation //! -use crate::{ - Sign, - BigDecimal, - BigDecimalRef, -}; - -use crate::stdlib::{ - ops::{Add, AddAssign, Neg}, - cmp::Ordering, -}; - -use num_bigint::{BigInt, BigUint}; -use crate::ten_to_the; +use super::*; + impl Add for BigDecimal { type Output = BigDecimal; diff --git a/src/impl_ops_mul.rs b/src/impl_ops_mul.rs index f13ec58..2b1df37 100644 --- a/src/impl_ops_mul.rs +++ b/src/impl_ops_mul.rs @@ -1,21 +1,9 @@ //! Multiplication operator trait implementation //! -use crate::{ - Sign, - BigDecimal, - BigDecimalRef, -}; - -use crate::stdlib::{ - mem::swap, - ops::{Mul, MulAssign, AddAssign, Neg}, - cmp::Ordering, -}; - -use num_bigint::{BigInt, BigUint}; -use num_traits::{Zero, One}; -use crate::ten_to_the; +use super::*; +use crate::stdlib::mem::swap; + impl Mul for BigDecimal { type Output = BigDecimal; diff --git a/src/impl_ops_rem.rs b/src/impl_ops_rem.rs index 4a4e3a4..8a1dc1d 100644 --- a/src/impl_ops_rem.rs +++ b/src/impl_ops_rem.rs @@ -2,6 +2,7 @@ use super::*; + impl Rem for BigDecimal { type Output = BigDecimal; diff --git a/src/impl_ops_sub.rs b/src/impl_ops_sub.rs index a6e2502..1961682 100644 --- a/src/impl_ops_sub.rs +++ b/src/impl_ops_sub.rs @@ -2,21 +2,8 @@ //! Multiplication operator trait implementation //! -use crate::{ - Sign, - BigDecimal, - BigDecimalRef, -}; - -use crate::stdlib::{ - mem::swap, - ops::{Sub, SubAssign, Neg}, - cmp::{self, Ordering}, -}; - -use num_bigint::{BigInt, BigUint}; -use num_traits::{Zero, One}; -use crate::ten_to_the; +use crate::*; + impl Sub for BigDecimal { type Output = BigDecimal; diff --git a/src/lib.rs b/src/lib.rs index a03ccb9..4de518d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -52,6 +52,9 @@ pub extern crate num_bigint; pub extern crate num_traits; extern crate num_integer; +#[cfg(test)] +extern crate paste; + #[cfg(feature = "serde")] extern crate serde; @@ -67,7 +70,9 @@ use self::stdlib::convert::TryFrom; use self::stdlib::default::Default; use self::stdlib::hash::{Hash, Hasher}; use self::stdlib::num::{ParseFloatError, ParseIntError}; -use self::stdlib::ops::{Add, AddAssign, Div, Mul, Neg, Rem, Sub}; +use self::stdlib::ops::{ + Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign, Rem +}; use self::stdlib::iter::Sum; use self::stdlib::str::FromStr; use self::stdlib::string::{String, ToString}; @@ -86,11 +91,9 @@ include!(concat!(env!("OUT_DIR"), "/default_precision.rs")); #[macro_use] mod macros; -#[cfg(test)] -extern crate paste; - // From, To, TryFrom impls mod impl_convert; + // Add, Sub, etc... mod impl_ops; mod impl_ops_add; @@ -105,7 +108,10 @@ mod impl_cmp; // Implementations of num_traits mod impl_num; +// construct BuildDecimals from strings and floats mod parsing; + +// Routines for rounding pub mod rounding; pub use rounding::RoundingMode; From 8765b17ee97fff1e53f31ecff1eac1d114cfe659 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Thu, 28 Sep 2023 20:18:48 -0400 Subject: [PATCH 33/77] Implement RemAssign --- src/impl_ops_rem.rs | 10 ++++++++++ src/lib.rs | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/impl_ops_rem.rs b/src/impl_ops_rem.rs index 8a1dc1d..56bad43 100644 --- a/src/impl_ops_rem.rs +++ b/src/impl_ops_rem.rs @@ -79,6 +79,13 @@ impl<'a, 'b> Rem<&'b BigDecimal> for &'a BigDecimal { } } +impl RemAssign<&BigDecimal> for BigDecimal { + fn rem_assign(&mut self, other: &BigDecimal) { + let rem = (&*self).rem(other); + *self = rem; + } +} + #[cfg(test)] mod test { @@ -103,6 +110,9 @@ mod test { assert_eq!(a.clone() % &b, c); assert_eq!(&a % b.clone(), c); assert_eq!(&a % &b, c); + + a %= &b; + assert_eq!(a, c); } }; } diff --git a/src/lib.rs b/src/lib.rs index 4de518d..b72f62c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,7 +71,7 @@ use self::stdlib::default::Default; use self::stdlib::hash::{Hash, Hasher}; use self::stdlib::num::{ParseFloatError, ParseIntError}; use self::stdlib::ops::{ - Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign, Rem + Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign, Rem, RemAssign, }; use self::stdlib::iter::Sum; use self::stdlib::str::FromStr; From ccc955c1a783111d5a85fc1b042914dc5b715fc9 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Thu, 28 Sep 2023 22:12:25 -0400 Subject: [PATCH 34/77] Fix function definitions in scripts/bigdecimal-property-tests --- scripts/bigdecimal-property-tests | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/bigdecimal-property-tests b/scripts/bigdecimal-property-tests index 66b120f..a4892f2 100755 --- a/scripts/bigdecimal-property-tests +++ b/scripts/bigdecimal-property-tests @@ -5,7 +5,7 @@ # Tests are defined in src/lib.tests.property-test.rs # -function enable_property_tests() { +enable_property_tests() { # enable property-test dependencies in Cargo sed -i.bak -e 's|# PROPERTY-TESTS: ||' Cargo.toml @@ -14,7 +14,7 @@ function enable_property_tests() { } -function restore_disabled_property_tests() { +restore_disabled_property_tests() { # Restore Cargo.toml with backup mv Cargo.toml.bak Cargo.toml mv build.rs.bak build.rs From df8b168be01bd7797de5f64f5b1a807ec8f6ef53 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Thu, 28 Sep 2023 23:06:36 -0400 Subject: [PATCH 35/77] Fix potential issues with scripts --- scripts/benchmark-bigdecimal | 10 +++++----- scripts/bigdecimal-property-tests | 2 +- scripts/fetch-benchmark-data.sh | 10 +++++----- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/scripts/benchmark-bigdecimal b/scripts/benchmark-bigdecimal index 04c08f2..bca871b 100755 --- a/scripts/benchmark-bigdecimal +++ b/scripts/benchmark-bigdecimal @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # # Run Criterion Benchmarks # @@ -14,7 +14,7 @@ mv Cargo.toml.bak Cargo.toml # store extra things for the benchmarking report -if [ ! -z "$BENCHMARK_EXTRAS" ]; then +if [ -n "$BENCHMARK_EXTRAS" ]; then cat < index.html @@ -36,12 +36,12 @@ EOF # Add svg plots to index html find target/criterion -name 'pdf.svg' -type f -print0 | sort -z | -while read -d $'\0' svg_file +while read -r -d $'\0' svg_file do - name=$(echo $svg_file | cut -d '/' -f 3) + name=$(echo "$svg_file" | cut -d '/' -f 3) sample_datafile=target/criterion/$name/new/sample.json - if [ -f $sample_datafile ]; then + if [ -f "$sample_datafile" ]; then echo "

$name" >> index.html else echo "

$name

" >> index.html diff --git a/scripts/bigdecimal-property-tests b/scripts/bigdecimal-property-tests index a4892f2..8196fcb 100755 --- a/scripts/bigdecimal-property-tests +++ b/scripts/bigdecimal-property-tests @@ -35,7 +35,7 @@ case "${CMD}" in test) enable_property_tests - cargo test $@ + cargo test "$@" restore_disabled_property_tests ;; diff --git a/scripts/fetch-benchmark-data.sh b/scripts/fetch-benchmark-data.sh index 83af9b1..26d8d73 100755 --- a/scripts/fetch-benchmark-data.sh +++ b/scripts/fetch-benchmark-data.sh @@ -16,16 +16,16 @@ function fetch_benchmark_bigdecimal_file() { local FILENAME="random-bigdecimals-$1.txt" local FILEPATH=$TEST_DATA_DIR/$FILENAME - if [ -e $FILEPATH ]; then + if [ -e "$FILEPATH" ]; then echo "exists: $FILEPATH" else local URL=${GITLAB_URL_PATTERN///$FILENAME} echo "fetching: $FILEPATH from $URL" - if [ $CURL ]; then - $CURL -s --fail -L $URL -o "$FILEPATH" - elif [ $WGET ]; then - $WGET --quiet $URL -O "$FILEPATH" + if [ "$CURL" ]; then + $CURL -s --fail -L "$URL" -o "$FILEPATH" + elif [ "$WGET" ]; then + "$WGET" --quiet "$URL" -O "$FILEPATH" else echo "No supported fetching program" fi From 3ee689cde63f2bcdcf7f71d076fdde754e402d63 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Fri, 29 Sep 2023 20:16:53 -0400 Subject: [PATCH 36/77] Ignore unused imports --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index b72f62c..6a04e3f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,6 +46,7 @@ #![allow(clippy::suspicious_arithmetic_impl)] #![allow(clippy::suspicious_op_assign_impl)] #![allow(clippy::redundant_field_names)] +#![allow(unused_imports)] pub extern crate num_bigint; From 257a088bd766d1451ecd1ad04fdf69b374c97213 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Tue, 26 Sep 2023 00:06:26 -0400 Subject: [PATCH 37/77] Move ten_to_the and count_decimal_digits to arithmetic module --- src/arithmetic/mod.rs | 73 ++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 78 +++++-------------------------------------- 2 files changed, 82 insertions(+), 69 deletions(-) create mode 100644 src/arithmetic/mod.rs diff --git a/src/arithmetic/mod.rs b/src/arithmetic/mod.rs new file mode 100644 index 0000000..4938f1f --- /dev/null +++ b/src/arithmetic/mod.rs @@ -0,0 +1,73 @@ +//! arithmetic routines + +use crate::*; + + +/// Return 10^pow +/// +/// Try to calculate this with fewest number of allocations +/// +pub(crate) fn ten_to_the(pow: u64) -> BigInt { + ten_to_the_uint(pow).into() +} + +/// Return 10^pow +pub(crate) fn ten_to_the_uint(pow: u64) -> BigUint { + if pow < 20 { + return BigUint::from(10u64.pow(pow as u32)); + } + + // linear case of 10^pow = 10^(19 * count + rem) + if pow < 590 { + let ten_to_nineteen = 10u64.pow(19); + + // count factors of 19 + let (count, rem) = pow.div_rem(&19); + + let mut res = BigUint::from(ten_to_nineteen); + for _ in 1..count { + res *= ten_to_nineteen; + } + if rem != 0 { + res *= 10u64.pow(rem as u32); + } + + return res; + } + + // use recursive algorithm where linear case might be too slow + let (quotient, rem) = pow.div_rem(&16); + let x = ten_to_the_uint(quotient); + + let x2 = &x * &x; + let x4 = &x2 * &x2; + let x8 = &x4 * &x4; + let res = &x8 * &x8; + + if rem == 0 { + res + } else { + res * 10u64.pow(rem as u32) + } +} + + +/// Return number of decimal digits in integer +pub(crate) fn count_decimal_digits(int: &BigInt) -> u64 { + count_decimal_digits_uint(int.magnitude()) +} + +/// Return number of decimal digits in unsigned integer +pub(crate) fn count_decimal_digits_uint(uint: &BigUint) -> u64 { + if uint.is_zero() { + return 1; + } + let mut digits = (uint.bits() as f64 / LOG2_10) as u64; + // guess number of digits based on number of bits in UInt + let mut num = ten_to_the(digits).to_biguint().expect("Ten to power is negative"); + while *uint >= num { + num *= 10u8; + digits += 1; + } + digits +} diff --git a/src/lib.rs b/src/lib.rs index 6a04e3f..4a36728 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -92,6 +92,9 @@ include!(concat!(env!("OUT_DIR"), "/default_precision.rs")); #[macro_use] mod macros; +// "low level" functions +mod arithmetic; + // From, To, TryFrom impls mod impl_convert; @@ -120,75 +123,12 @@ pub use rounding::RoundingMode; mod context; pub use context::Context; -/// Return 10^pow -/// -/// Try to calculate this with fewest number of allocations -/// -fn ten_to_the(pow: u64) -> BigInt { - ten_to_the_uint(pow).into() -} - -/// Return 10^pow -fn ten_to_the_uint(pow: u64) -> BigUint { - if pow < 20 { - return BigUint::from(10u64.pow(pow as u32)); - } - - // linear case of 10^pow = 10^(19 * count + rem) - if pow < 590 { - let ten_to_nineteen = 10u64.pow(19); - - // count factors of 19 - let (count, rem) = pow.div_rem(&19); - - let mut res = BigUint::from(ten_to_nineteen); - for _ in 1..count { - res *= ten_to_nineteen; - } - if rem != 0 { - res *= 10u64.pow(rem as u32); - } - - return res; - } - - // use recursive algorithm where linear case might be too slow - let (quotient, rem) = pow.div_rem(&16); - let x = ten_to_the_uint(quotient); - - let x2 = &x * &x; - let x4 = &x2 * &x2; - let x8 = &x4 * &x4; - let res = &x8 * &x8; - - if rem == 0 { - res - } else { - res * 10u64.pow(rem as u32) - } -} - - -/// Return number of decimal digits in integer -fn count_decimal_digits(int: &BigInt) -> u64 { - count_decimal_digits_uint(int.magnitude()) -} - -/// Return number of decimal digits in unsigned integer -fn count_decimal_digits_uint(uint: &BigUint) -> u64 { - if uint.is_zero() { - return 1; - } - let mut digits = (uint.bits() as f64 / LOG2_10) as u64; - // guess number of digits based on number of bits in UInt - let mut num = ten_to_the(digits).to_biguint().expect("Ten to power is negative"); - while *uint >= num { - num *= 10u8; - digits += 1; - } - digits -} - +use arithmetic::{ + ten_to_the, + ten_to_the_uint, + count_decimal_digits, + count_decimal_digits_uint, +}; /// Internal function used for rounding /// From cb2248905933cd75ccb00da75188cf7a20a3d904 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Fri, 29 Sep 2023 02:25:34 -0400 Subject: [PATCH 38/77] Add new implementation of sqrt using Context --- src/arithmetic/mod.rs | 1 + src/arithmetic/sqrt.rs | 187 +++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 83 +++++------------- 3 files changed, 210 insertions(+), 61 deletions(-) create mode 100644 src/arithmetic/sqrt.rs diff --git a/src/arithmetic/mod.rs b/src/arithmetic/mod.rs index 4938f1f..76b1b83 100644 --- a/src/arithmetic/mod.rs +++ b/src/arithmetic/mod.rs @@ -2,6 +2,7 @@ use crate::*; +pub(crate) mod sqrt; /// Return 10^pow /// diff --git a/src/arithmetic/sqrt.rs b/src/arithmetic/sqrt.rs new file mode 100644 index 0000000..1d52e8a --- /dev/null +++ b/src/arithmetic/sqrt.rs @@ -0,0 +1,187 @@ +//! square root implementation + +use crate::*; + + +fn impl_division(mut num: BigUint, den: &BigUint, mut scale: i64, max_precision: u64) -> BigDecimal { + use Sign::Plus; + + // quick zero check + if num.is_zero() { + return BigDecimal::new(BigInt::from_biguint(Plus, num), 0); + } + + // shift digits until numerator is larger than denominator (set scale appropriately) + while num < *den { + scale += 1; + num *= 10u8; + } + + // first division + let (mut quotient, mut remainder) = num.div_rem(den); + + // division complete + if remainder.is_zero() { + return BigDecimal { + int_val: BigInt::from_biguint(Plus, quotient), + scale: scale, + }; + } + + let mut precision = count_decimal_digits_uint("ient); + + // shift remainder by 1 decimal; + // quotient will be 1 digit upon next division + remainder *= 10u8; + + while !remainder.is_zero() && precision < max_precision { + let (q, r) = remainder.div_rem(den); + quotient = quotient * 10u8 + q; + remainder = r * 10u8; + + precision += 1; + scale += 1; + } + + if !remainder.is_zero() { + // round final number with remainder + quotient += get_rounding_term_uint(&remainder.div(den)); + } + + BigDecimal::new(BigInt::from_biguint(Plus, quotient), scale) +} + +fn get_rounding_term_uint(num: &BigUint) -> u8 { + if num.is_zero() { + return 0; + } + + let digits = (num.bits() as f64 / LOG2_10) as u64; + let mut n = ten_to_the_uint(digits); + + // loop-method + loop { + if *num < n { + return 1; + } + n *= 5u8; + if *num < n { + return 0; + } + n *= 2u8; + } + + // string-method + // let s = format!("{}", num); + // let high_digit = u8::from_str(&s[0..1]).unwrap(); + // if high_digit < 5 { 0 } else { 1 } +} + +pub(crate) fn impl_sqrt(n: &BigUint, scale: i64, ctx: &Context) -> BigDecimal { + // make guess + let guess = { + let magic_guess_scale = 1.1951678538495576_f64; + let initial_guess = (n.bits() as f64 - scale as f64 * LOG2_10) / 2.0; + let res = magic_guess_scale * exp2(initial_guess); + + if res.is_normal() { + BigDecimal::try_from(res).unwrap() + } else { + // can't guess with float - just guess magnitude + let scale = (n.bits() as f64 / -LOG2_10 + scale as f64).round() as i64; + BigDecimal::new(BigInt::from(1), scale / 2) + } + }; + + // // wikipedia example - use for testing the algorithm + // if self == &BigDecimal::from_str("125348").unwrap() { + // running_result = BigDecimal::from(600) + // } + + // TODO: Use context variable to set precision + let max_precision = ctx.precision().get(); + + let next_iteration = move |r: BigDecimal| { + // division needs to be precise to (at least) one extra digit + let tmp = impl_division( + n.clone(), + &r.int_val.magnitude(), + scale - r.scale, + max_precision + 1, + ); + + // half will increase precision on each iteration + (tmp + r).half() + }; + + // calculate first iteration + let mut running_result = next_iteration(guess); + + let mut prev_result = BigDecimal::one(); + let mut result = BigDecimal::zero(); + + // TODO: Prove that we don't need to arbitrarily limit iterations + // and that convergence can be calculated + while prev_result != result { + // store current result to test for convergence + prev_result = result; + + // calculate next iteration + running_result = next_iteration(running_result); + + // 'result' has clipped precision, 'running_result' has full precision + result = if running_result.digits() > max_precision { + running_result.with_prec(max_precision) + } else { + running_result.clone() + }; + } + + result +} + + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn case_sqrt_3242053850483855em13() { + let d: BigDecimal = "324.2053850483855".parse().unwrap(); + + let digitref = d.to_ref(); + let (_, scale, uint) = digitref.as_parts(); + // let prec = NonZeroUsize::new(11); + // let ctx = Context::new(prec.unwrap(), RoundingMode::Up); + let ctx = Context::default() + .with_prec(11).unwrap() + .with_rounding_mode(RoundingMode::Down); + + let s = impl_sqrt(uint, scale, &ctx); + let expected: BigDecimal = "18.005704236".parse().unwrap(); + assert_eq!(s, expected); + + let ctx = Context::default() + .with_prec(31).unwrap() + .with_rounding_mode(RoundingMode::Up); + + let s = impl_sqrt(uint, scale, &ctx); + let expected: BigDecimal = "18.00570423639090823994825477228".parse().unwrap(); + assert_eq!(s, expected); + } + + #[test] + fn case_sqrt_5085019992340351em25() { + let d: BigDecimal = "5.085019992340351e-10".parse().unwrap(); + + let digitref = d.to_ref(); + let (_, scale, uint) = digitref.as_parts(); + let ctx = Context::default() + .with_prec(25).unwrap() + .with_rounding_mode(RoundingMode::Down); + + let s = impl_sqrt(uint, scale, &ctx); + let expected: BigDecimal = "0.00002254998889653906459324292".parse().unwrap(); + assert_eq!(s, expected); + } +} diff --git a/src/lib.rs b/src/lib.rs index 4a36728..9589404 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -621,6 +621,12 @@ impl BigDecimal { /// ``` #[inline] pub fn sqrt(&self) -> Option { + self.sqrt_with_context(&Context::default()) + } + + /// Take the square root of the number, using context for precision and rounding + /// + pub fn sqrt_with_context(&self, ctx: &Context) -> Option { if self.is_zero() || self.is_one() { return Some(self.clone()); } @@ -628,66 +634,10 @@ impl BigDecimal { return None; } - // make guess - 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 * exp2(initial_guess); - - if res.is_normal() { - BigDecimal::try_from(res).unwrap() - } else { - // can't guess with float - just guess magnitude - let scale = (self.int_val.bits() as f64 / -LOG2_10 + self.scale as f64).round() as i64; - BigDecimal::new(BigInt::from(1), scale / 2) - } - }; - - // // wikipedia example - use for testing the algorithm - // if self == &BigDecimal::from_str("125348").unwrap() { - // running_result = BigDecimal::from(600) - // } - - // TODO: Use context variable to set precision - let max_precision = DEFAULT_PRECISION; - - let next_iteration = move |r: BigDecimal| { - // division needs to be precise to (at least) one extra digit - let tmp = impl_division( - self.int_val.clone(), - &r.int_val, - self.scale - r.scale, - max_precision + 1, - ); - - // half will increase precision on each iteration - (tmp + r).half() - }; - - // calculate first iteration - let mut running_result = next_iteration(guess); + let uint = self.int_val.magnitude(); + let result = arithmetic::sqrt::impl_sqrt(uint, self.scale, ctx); - let mut prev_result = BigDecimal::one(); - let mut result = BigDecimal::zero(); - - // TODO: Prove that we don't need to arbitrarily limit iterations - // and that convergence can be calculated - while prev_result != result { - // store current result to test for convergence - prev_result = result; - - // calculate next iteration - running_result = next_iteration(running_result); - - // 'result' has clipped precision, 'running_result' has full precision - result = if running_result.digits() > max_precision { - running_result.with_prec(max_precision) - } else { - running_result.clone() - }; - } - - return Some(result); + Some(result) } /// Take the cube root of the number @@ -1123,8 +1073,6 @@ impl One for BigDecimal { } - - fn impl_division(mut num: BigInt, den: &BigInt, mut scale: i64, max_precision: u64) -> BigDecimal { // quick zero check if num.is_zero() { @@ -1349,6 +1297,19 @@ impl BigDecimalRef<'_> { scale: self.scale, } } + + /// Take square root of this number + pub fn sqrt_with_context(&self, ctx: &Context) -> Option { + use Sign::*; + + let (sign, scale, uint) = self.as_parts(); + + match sign { + Minus => None, + NoSign => Some(Zero::zero()), + Plus => Some(arithmetic::sqrt::impl_sqrt(uint, scale, ctx)), + } + } } impl<'a> From<&'a BigDecimal> for BigDecimalRef<'a> { From 62823c2a1b0ae0477832a0cde8057f3f776f3419 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Fri, 29 Sep 2023 23:43:59 -0400 Subject: [PATCH 39/77] Use crate::* in context --- src/context.rs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/context.rs b/src/context.rs index abab31f..d4931b7 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,23 +1,16 @@ //! Define arithmetical context //! -use crate::rounding::RoundingMode; +use crate::*; use stdlib::num::NonZeroU64; -use stdlib; -use num_traits::{ToPrimitive, Zero}; - -use crate::{ - Sign, - BigDecimal, - BigDecimalRef -}; // const DEFAULT_PRECISION: u64 = ${RUST_BIGDECIMAL_DEFAULT_PRECISION} or 100; include!(concat!(env!("OUT_DIR"), "/default_precision.rs")); // const DEFAULT_ROUNDING_MODE: RoundingMode = ${RUST_BIGDECIMAL_DEFAULT_ROUNDING_MODE} or HalfUp; include!(concat!(env!("OUT_DIR"), "/default_rounding_mode.rs")); + /// Mathematical Context /// /// Stores rules for numerical operations, such as how to round and From 8c2d713cceba7956aa1ed0dd1d5eb7f2ed0f791d Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sun, 1 Oct 2023 22:18:06 -0400 Subject: [PATCH 40/77] Correct minor typos --- benches/arithmetic.rs | 2 +- build.rs | 2 +- src/context.rs | 2 +- src/impl_cmp.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/benches/arithmetic.rs b/benches/arithmetic.rs index b6b3299..04ff995 100644 --- a/benches/arithmetic.rs +++ b/benches/arithmetic.rs @@ -1,4 +1,4 @@ -//! Benchmarks for arithmetic opertaion +//! Benchmarks for arithmetic operation extern crate criterion; extern crate bigdecimal; diff --git a/build.rs b/build.rs index 3c47214..8e43e1a 100644 --- a/build.rs +++ b/build.rs @@ -19,7 +19,7 @@ fn main() { } -/// Create default_precision.rs, containg definition of constant DEFAULT_PRECISION loaded in src/lib.rs +/// Create default_precision.rs, containing definition of constant DEFAULT_PRECISION loaded in src/lib.rs fn write_default_precision_file(outdir: &Path) { let env_var = env::var("RUST_BIGDECIMAL_DEFAULT_PRECISION").unwrap_or_else(|_| "100".to_owned()); println!("cargo:rerun-if-env-changed=RUST_BIGDECIMAL_DEFAULT_PRECISION"); diff --git a/src/context.rs b/src/context.rs index d4931b7..d5601ca 100644 --- a/src/context.rs +++ b/src/context.rs @@ -24,7 +24,7 @@ include!(concat!(env!("OUT_DIR"), "/default_rounding_mode.rs")); /// | `RUST_BIGDECIMAL_DEFAULT_PRECISION` | digit precision | 100 | /// | `RUST_BIGDECIMAL_DEFAULT_ROUNDING_MODE` | rounding-mode | HalfEven | /// -/// It is recommended that the user set explict values of a Context and *not* +/// It is recommended that the user set explicit values of a Context and *not* /// rely on compile time constants, but the option is there if necessary. /// #[derive(Debug, Clone)] diff --git a/src/impl_cmp.rs b/src/impl_cmp.rs index feeaa43..ecef30a 100644 --- a/src/impl_cmp.rs +++ b/src/impl_cmp.rs @@ -1,4 +1,4 @@ -//! Implementation of comparsion operations +//! Implementation of comparison operations //! //! Comparisons between decimals and decimal refs //! are not directly supported as we lose some type From 8d4781af1d8fc31365aa8943cd50c6d88a08b7d7 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sun, 8 Oct 2023 14:18:28 -0400 Subject: [PATCH 41/77] Fix typo in doc-comment --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 9589404..e43e27d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -112,7 +112,7 @@ mod impl_cmp; // Implementations of num_traits mod impl_num; -// construct BuildDecimals from strings and floats +// construct BigDecimals from strings and floats mod parsing; // Routines for rounding From f1e8d5f37201f99241935cabe7b4768c4d34befb Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sun, 8 Oct 2023 15:30:14 -0400 Subject: [PATCH 42/77] Add method take_with_sign --- src/lib.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index e43e27d..77d80cd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -356,6 +356,21 @@ impl BigDecimal { } } + /// Take and return bigdecimal with the given sign + /// + /// The Sign value `NoSign` is ignored: only use Plus & Minus + /// + pub(crate) fn take_with_sign(self, sign: Sign) -> BigDecimal { + let BigDecimal { scale, mut int_val } = self; + if int_val.sign() != sign && sign != Sign::NoSign { + int_val = int_val.neg(); + } + BigDecimal { + int_val: int_val, + scale: scale, + } + } + /// Return a new BigDecimal object with precision set to new value /// /// ``` From ee4fcb23aba7300b6eab722af274f308c54648e4 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sun, 8 Oct 2023 15:26:46 -0400 Subject: [PATCH 43/77] Move cbrt implementation to src/arithmetic/cbrt.rs --- src/arithmetic/cbrt.rs | 84 ++++++++++++++++++++++++++++++++++++++++++ src/arithmetic/mod.rs | 1 + src/lib.rs | 68 +++++----------------------------- 3 files changed, 95 insertions(+), 58 deletions(-) create mode 100644 src/arithmetic/cbrt.rs diff --git a/src/arithmetic/cbrt.rs b/src/arithmetic/cbrt.rs new file mode 100644 index 0000000..fa0d959 --- /dev/null +++ b/src/arithmetic/cbrt.rs @@ -0,0 +1,84 @@ +//! Implementation of cube-root algorithm + +use crate::*; +use num_bigint::BigUint; + + +/// implementation of cuberoot - always positive +pub(crate) fn impl_cbrt(n: &BigUint, scale: i64, ctx: &Context) -> BigDecimal { + // make guess based on number of bits in the number + let guess = make_cbrt_guess(n.bits(), scale); + + let three = BigInt::from(3); + + let n = BigInt::from_biguint(Sign::Plus, n.clone()); + + let max_precision = ctx.precision().get(); + + let next_iteration = move |r: BigDecimal| { + let sqrd = r.square(); + let tmp = impl_division( + n.clone(), + &sqrd.int_val, + scale - sqrd.scale, + max_precision + 1, + ); + let tmp = tmp + r.double(); + impl_division(tmp.int_val, &three, tmp.scale, max_precision + 1) + }; + + // result initial + let mut running_result = next_iteration(guess); + + let mut prev_result = BigDecimal::one(); + let mut result = BigDecimal::zero(); + + // TODO: Prove that we don't need to arbitrarily limit iterations + // and that convergence can be calculated + while prev_result != result { + // store current result to test for convergence + prev_result = result; + + running_result = next_iteration(running_result); + + // result has clipped precision, running_result has full precision + result = if running_result.digits() > max_precision { + running_result.with_prec(max_precision) + } else { + running_result.clone() + }; + } + + return result; +} + +/// Find initial cbrt guess based on number of bits in integer and the scale +/// +/// ```math +/// 2^bit_count * 10^-scale <= *n* < 2^(bit_count+1) * 10^-scale +/// +/// cbrt(n2^bit_count * 10^-scale) +/// cbrt(2^bit_count * 10^-scale) +/// => Exp2[1/3 * Log2[2^bit_count * 10^-scale]] +/// => Exp2[1/3 * (bit_count - scale * Log2[10]] +/// ``` +/// +fn make_cbrt_guess(bit_count: u64, scale: i64) -> BigDecimal { + // weight of cube root average above minimum within range: 3/4*2^(4/3) + let magic_guess_scale = 1.1398815748423097_f64; + + let bit_count = bit_count as f64; + let scale = scale as f64; + + let initial_guess = (bit_count - scale * LOG2_10) / 3.0; + let res = magic_guess_scale * exp2(initial_guess); + + match BigDecimal::try_from(res) { + Ok(res) => res, + Err(_) => { + // can't guess with float - just guess magnitude + let scale = (scale - bit_count / LOG2_10).round() as i64; + BigDecimal::new(BigInt::from(1), scale / 3) + } + } +} diff --git a/src/arithmetic/mod.rs b/src/arithmetic/mod.rs index 76b1b83..0321aad 100644 --- a/src/arithmetic/mod.rs +++ b/src/arithmetic/mod.rs @@ -3,6 +3,7 @@ use crate::*; pub(crate) mod sqrt; +pub(crate) mod cbrt; /// Return 10^pow /// diff --git a/src/lib.rs b/src/lib.rs index 77d80cd..297ff1d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -655,72 +655,24 @@ impl BigDecimal { Some(result) } - /// Take the cube root of the number + /// Take the cube root of the number, using default context /// #[inline] pub fn cbrt(&self) -> BigDecimal { + self.cbrt_with_context(&Context::default()) + } + + /// Take cube root of self, using properties of context + pub fn cbrt_with_context(&self, ctx: &Context) -> BigDecimal { if self.is_zero() || self.is_one() { return self.clone(); } - if self.is_negative() { - return -self.abs().cbrt(); - } - - // make guess - 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 * exp2(initial_guess); - - if res.is_normal() { - BigDecimal::try_from(res).unwrap() - } else { - // can't guess with float - just guess magnitude - let scale = (self.int_val.bits() as f64 / LOG2_10 - self.scale as f64).round() as i64; - BigDecimal::new(BigInt::from(1), -scale / 3) - } - }; - - // TODO: Use context variable to set precision - let max_precision = DEFAULT_PRECISION; - let three = BigDecimal::from(3); - - let next_iteration = move |r: BigDecimal| { - let sqrd = r.square(); - let tmp = impl_division( - self.int_val.clone(), - &sqrd.int_val, - self.scale - sqrd.scale, - max_precision + 1, - ); - let tmp = tmp + r.double(); - impl_division(tmp.int_val, &three.int_val, tmp.scale - three.scale, max_precision + 1) - }; - - // result initial - let mut running_result = next_iteration(guess); - - let mut prev_result = BigDecimal::one(); - let mut result = BigDecimal::zero(); - - // TODO: Prove that we don't need to arbitrarily limit iterations - // and that convergence can be calculated - while prev_result != result { - // store current result to test for convergence - prev_result = result; - - running_result = next_iteration(running_result); - - // result has clipped precision, running_result has full precision - result = if running_result.digits() > max_precision { - running_result.with_prec(max_precision) - } else { - running_result.clone() - }; - } + let uint = self.int_val.magnitude(); + let result = arithmetic::cbrt::impl_cbrt(uint, self.scale, ctx); - return result; + // always copy sign + result.take_with_sign(self.sign()) } /// Compute the reciprical of the number: x-1 From 584ddc526300e5e840da0f2dca0ca7b27890ed46 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sun, 8 Oct 2023 20:29:17 -0400 Subject: [PATCH 44/77] Move implementation of inverse into src/arithmetic/inverse.rs --- src/arithmetic/inverse.rs | 146 ++++++++++++++++++++++++++++++++++++++ src/arithmetic/mod.rs | 1 + src/lib.rs | 59 +++------------ 3 files changed, 156 insertions(+), 50 deletions(-) create mode 100644 src/arithmetic/inverse.rs diff --git a/src/arithmetic/inverse.rs b/src/arithmetic/inverse.rs new file mode 100644 index 0000000..6a51289 --- /dev/null +++ b/src/arithmetic/inverse.rs @@ -0,0 +1,146 @@ +//! inverse implementation + +use crate::*; + +/// Implementation of inverse: (1/n) +pub(crate) fn impl_inverse_uint_scale(n: &BigUint, scale: i64, ctx: &Context) -> BigDecimal { + let guess = make_inv_guess(n.bits(), scale); + let max_precision = ctx.precision().get(); + + let s = BigDecimal::new(BigInt::from_biguint(Sign::Plus, n.clone()), scale); + let two = BigDecimal::from(2); + + let next_iteration = move |r: BigDecimal| { + let tmp = &two - &s * &r; + r * tmp + }; + + // calculate first iteration + let mut running_result = next_iteration(guess); + + let mut prev_result = BigDecimal::one(); + let mut result = BigDecimal::zero(); + + // TODO: Prove that we don't need to arbitrarily limit iterations + // and that convergence can be calculated + while prev_result != result { + // store current result to test for convergence + prev_result = result; + + // calculate next iteration + running_result = next_iteration(running_result).with_prec(max_precision + 2); + + // 'result' has clipped precision, 'running_result' has full precision + result = if running_result.digits() > max_precision { + running_result.with_precision_round(ctx.precision(), ctx.rounding_mode()) + } else { + running_result.clone() + }; + } + + return result; +} + +fn make_inv_guess(bit_count: u64, scale: i64) -> BigDecimal { + // scale by ln(2) + let magic_factor = 0.6931471805599453_f64; + + let bit_count = bit_count as f64; + let scale = scale as f64; + + let initial_guess = scale * LOG2_10 - bit_count; + let res = magic_factor * exp2(initial_guess); + + match BigDecimal::try_from(res) { + Ok(res) => res, + Err(_) => { + // can't guess with float - just guess magnitude + let scale = (bit_count / LOG2_10 + scale).round() as i64; + BigDecimal::new(BigInt::from(1), -scale) + } + } +} + + +#[cfg(test)] +mod test { + use super::*; + use stdlib::num::NonZeroU64; + + macro_rules! impl_case { + ($name:ident: $prec:literal, $round:ident => $expected:literal) => { + #[test] + fn $name() { + let n = test_input(); + let prec = NonZeroU64::new($prec).unwrap(); + let rounding = RoundingMode::$round; + let ctx = Context::new(prec, rounding); + + let result = n.inverse_with_context(&ctx); + + let expected = $expected.parse().unwrap(); + assert_eq!(&result, &expected); + + let product = result * &n; + let epsilon = BigDecimal::new(BigInt::one(), $prec - 1); + let diff = (BigDecimal::one() - &product).abs(); + assert!(diff < epsilon); + } + }; + } + + mod invert_seven { + use super::*; + + fn test_input() -> BigDecimal { + BigDecimal::from(7u8) + } + + impl_case!(case_prec10_round_down: 10, Down => "0.1428571428"); + impl_case!(case_prec10_round_up: 10, Up => "0.1428571429"); + + impl_case!(case_prec11_round_ceiling: 11, Ceiling => "0.14285714286"); + } + + + #[test] + fn inv_random_number() { + let n = BigDecimal::try_from(0.08121970592310568).unwrap(); + + let ctx = Context::new(NonZeroU64::new(40).unwrap(), RoundingMode::Down); + let i = n.inverse_with_context(&ctx); + assert_eq!(&i, &"12.31228294456944530942557443718279245563".parse().unwrap()); + + let product = i * &n; + assert!(BigDecimal::one() - &product < "1e-39".parse().unwrap()); + } + + #[cfg(property_tests)] + mod prop { + use super::*; + use proptest::*; + use num_traits::FromPrimitive; + + proptest! { + + #[test] + fn inverse_multiplies_to_one(f: f64, prec in 1..100u64) { + // ignore non-normal numbers + prop_assume!(f.is_normal()); + prop_assume!(f != 0.0); + + let n = BigDecimal::from_f64(f).unwrap(); + + let ctx = Context::new(NonZeroU64::new(prec).unwrap(), RoundingMode::Up); + let i = n.inverse_with_context(&ctx); + let product = &i * &n; + + // accurate to precision minus one (due to rounding) + let epsilon = BigDecimal::new(1.into(), prec as i64 - 1); + let diff_from_one = BigDecimal::one() - &product; + + prop_assert!(diff_from_one.abs() < epsilon, "{} >= {}", diff_from_one.abs(), epsilon); + } + } + } +} diff --git a/src/arithmetic/mod.rs b/src/arithmetic/mod.rs index 0321aad..614503d 100644 --- a/src/arithmetic/mod.rs +++ b/src/arithmetic/mod.rs @@ -4,6 +4,7 @@ use crate::*; pub(crate) mod sqrt; pub(crate) mod cbrt; +pub(crate) mod inverse; /// Return 10^pow /// diff --git a/src/lib.rs b/src/lib.rs index 297ff1d..5b911be 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -678,61 +678,20 @@ impl BigDecimal { /// Compute the reciprical of the number: x-1 #[inline] pub fn inverse(&self) -> BigDecimal { + self.inverse_with_context(&Context::default()) + } + + /// Return inverse of self, rounding with ctx + pub fn inverse_with_context(&self, ctx: &Context) -> BigDecimal { if self.is_zero() || self.is_one() { return self.clone(); } - if self.is_negative() { - return self.abs().inverse().neg(); - } - let guess = { - let bits = self.int_val.bits() as f64; - let scale = self.scale as f64; - - let magic_factor = 0.721507597259061_f64; - let initial_guess = scale * LOG2_10 - bits; - let res = magic_factor * exp2(initial_guess); - - if res.is_normal() { - BigDecimal::try_from(res).unwrap() - } else { - // can't guess with float - just guess magnitude - let scale = (bits / LOG2_10 + scale).round() as i64; - BigDecimal::new(BigInt::from(1), -scale) - } - }; - - let max_precision = DEFAULT_PRECISION; - let next_iteration = move |r: BigDecimal| { - let two = BigDecimal::from(2); - let tmp = two - self * &r; - - r * tmp - }; - - // calculate first iteration - let mut running_result = next_iteration(guess); - - let mut prev_result = BigDecimal::one(); - let mut result = BigDecimal::zero(); - // TODO: Prove that we don't need to arbitrarily limit iterations - // and that convergence can be calculated - while prev_result != result { - // store current result to test for convergence - prev_result = result; - - // calculate next iteration - running_result = next_iteration(running_result).with_prec(max_precision); - - // 'result' has clipped precision, 'running_result' has full precision - result = if running_result.digits() > max_precision { - running_result.with_prec(max_precision) - } else { - running_result.clone() - }; - } + let uint = self.int_val.magnitude(); + let result = arithmetic::inverse::impl_inverse_uint_scale(uint, self.scale, ctx); - return result; + // always copy sign + result.take_with_sign(self.sign()) } /// Return number rounded to round_digits precision after the decimal point From 3c9bb6f94a31c9e2eda85ccf84d72bb75a671115 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sun, 8 Oct 2023 20:46:25 -0400 Subject: [PATCH 45/77] Move cbrt tests into src/arithmetic/cbrt.rs --- src/arithmetic/cbrt.rs | 73 ++++++++++++++++++++++++++++++++++++++++-- src/lib.rs | 19 +---------- 2 files changed, 71 insertions(+), 21 deletions(-) diff --git a/src/arithmetic/cbrt.rs b/src/arithmetic/cbrt.rs index fa0d959..8887fb9 100644 --- a/src/arithmetic/cbrt.rs +++ b/src/arithmetic/cbrt.rs @@ -5,7 +5,7 @@ use num_bigint::BigUint; /// implementation of cuberoot - always positive -pub(crate) fn impl_cbrt(n: &BigUint, scale: i64, ctx: &Context) -> BigDecimal { +pub(crate) fn impl_cbrt_uint_scale(n: &BigUint, scale: i64, ctx: &Context) -> BigDecimal { // make guess based on number of bits in the number let guess = make_cbrt_guess(n.bits(), scale); @@ -24,7 +24,7 @@ pub(crate) fn impl_cbrt(n: &BigUint, scale: i64, ctx: &Context) -> BigDecimal { max_precision + 1, ); let tmp = tmp + r.double(); - impl_division(tmp.int_val, &three, tmp.scale, max_precision + 1) + impl_division(tmp.int_val, &three, tmp.scale, max_precision + 3) }; // result initial @@ -43,7 +43,7 @@ pub(crate) fn impl_cbrt(n: &BigUint, scale: i64, ctx: &Context) -> BigDecimal { // result has clipped precision, running_result has full precision result = if running_result.digits() > max_precision { - running_result.with_prec(max_precision) + running_result.with_precision_round(ctx.precision(), ctx.rounding_mode()) } else { running_result.clone() }; @@ -82,3 +82,70 @@ fn make_cbrt_guess(bit_count: u64, scale: i64) -> BigDecimal { } } } + + +#[cfg(test)] +mod test { + use super::*; + use stdlib::num::NonZeroU64; + + #[test] + fn test_cbrt() { + let vals = vec![ + ("0.00", "0"), + ("1.00", "1"), + ("1.001", "1.000333222283909495175449559955220102010284758197360454054345461242739715702641939155238095670636841"), + ("10", "2.154434690031883721759293566519350495259344942192108582489235506346411106648340800185441503543243276"), + ("13409.179789484375", "23.7575"), + ("-59283293e25", "-84006090355.84281237113712383191213626687332139035750444925827809487776780721673264524620270275301685"), + ("94213372931e-127", "2.112049945275324414051072540210070583697242797173805198575907094646677475250362108901530353886613160E-39"), + ]; + for &(x, y) in vals.iter() { + let a = BigDecimal::from_str(x).unwrap().cbrt(); + let b = BigDecimal::from_str(y).unwrap(); + assert_eq!(a, b); + } + } + + + #[test] + fn test_cbrt_prec15() { + let vals = vec![ + ("0.00", "0"), + ("1.00", "1"), + ("1.001", "1.00033322228390"), + ("10", "2.15443469003188"), + ("13409.179789484375", "23.7575"), + ("-59283293e25", "-84006090355.8428"), + ("94213372931e-127", "2.11204994527532E-39"), + ]; + + let ctx = Context::new(NonZeroU64::new(15).unwrap(), RoundingMode::Down); + + for &(x, y) in vals.iter() { + let a = BigDecimal::from_str(x).unwrap().cbrt_with_context(&ctx); + let b = y.parse().unwrap(); + assert_eq!(a, b); + } + } + + #[cfg(property_tests)] + mod prop { + use super::*; + use proptest::*; + use num_traits::FromPrimitive; + + proptest! { + #[test] + fn cbrt_of_cube_is_self(f: f64, prec in 15..50u64) { + // ignore non-normal numbers + prop_assume!(f.is_normal()); + + let n = BigDecimal::from_f64(f).unwrap().with_prec(prec); + let n_cubed = n.cube(); + let x = n_cubed.cbrt(); + prop_assert_eq!(x, n); + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 5b911be..247b80b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -669,7 +669,7 @@ impl BigDecimal { } let uint = self.int_val.magnitude(); - let result = arithmetic::cbrt::impl_cbrt(uint, self.scale, ctx); + let result = arithmetic::cbrt::impl_cbrt_uint_scale(uint, self.scale, ctx); // always copy sign result.take_with_sign(self.sign()) @@ -2018,23 +2018,6 @@ mod bigdecimal_tests { } } - #[test] - fn test_cbrt() { - let vals = vec![ - ("0.00", "0"), - ("1.00", "1"), - ("1.001", "1.000333222283909495175449559955220102010284758197360454054345461242739715702641939155238095670636841"), - ("10", "2.154434690031883721759293566519350495259344942192108582489235506346411106648340800185441503543243276"), - ("-59283293e25", "-84006090355.84281237113712383191213626687332139035750444925827809487776780721673264524620270275301685"), - ("94213372931e-127", "2.112049945275324414051072540210070583697242797173805198575907094646677475250362108901530353886613160E-39"), - ]; - for &(x, y) in vals.iter() { - let a = BigDecimal::from_str(x).unwrap().cbrt(); - let b = BigDecimal::from_str(y).unwrap(); - assert_eq!(a, b); - } - } - mod double { use super::*; From 4a30485c7d2eda391f7309d3f6f5afd7ede34ffd Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sun, 8 Oct 2023 23:07:27 -0400 Subject: [PATCH 46/77] Move sqrt tests into src/arithmetic/sqrt.rs --- src/arithmetic/sqrt.rs | 75 ++++++++++++++++++++++++++++++++++++++++-- src/lib.rs | 53 ----------------------------- 2 files changed, 73 insertions(+), 55 deletions(-) diff --git a/src/arithmetic/sqrt.rs b/src/arithmetic/sqrt.rs index 1d52e8a..d2a56df 100644 --- a/src/arithmetic/sqrt.rs +++ b/src/arithmetic/sqrt.rs @@ -145,14 +145,65 @@ pub(crate) fn impl_sqrt(n: &BigUint, scale: i64, ctx: &Context) -> BigDecimal { mod test { use super::*; + #[test] + fn test_sqrt() { + let vals = vec![ + ("1e-232", "1e-116"), + ("1.00", "1"), + ("1.001", "1.000499875062460964823258287700109753027590031219780479551442971840836093890879944856933288426795152"), + ("100", "10"), + ("49", "7"), + (".25", ".5"), + ("0.0152399025", ".12345"), + ("152399025", "12345"), + (".00400", "0.06324555320336758663997787088865437067439110278650433653715009705585188877278476442688496216758600590"), + (".1", "0.3162277660168379331998893544432718533719555139325216826857504852792594438639238221344248108379300295"), + ("2", "1.414213562373095048801688724209698078569671875376948073176679737990732478462107038850387534327641573"), + ("125348", "354.0451948551201563108487193176101314241016013304294520812832530590100407318465590778759640828114535"), + ("18446744073709551616.1099511", "4294967296.000000000012799992691725492477397918722952224079252026972356303360555051219312462698703293"), + ("3.141592653589793115997963468544185161590576171875", "1.772453850905515992751519103139248439290428205003682302442979619028063165921408635567477284443197875"), + (".000000000089793115997963468544185161590576171875", "0.000009475922962855041517561783740144225422359796851494316346796373337470068631250135521161989831460407155"), + ("0.7177700109762963922745342343167413624881759290454997218753321040760896053150388903350654937434826216803814031987652326749140535150336357405672040727695124057298138872112244784753994931999476811850580200000000000000000000000000000000", "0.8472130847527653667042980517799020703921106560594525833177762276594388966885185567535692987624493813"), + ("0.01234567901234567901234567901234567901234567901234567901234567901234567901234567901234567901234567901", "0.1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"), + ("0.1108890000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000444", "0.3330000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000667"), + ]; + for &(x, y) in vals.iter() { + let a = BigDecimal::from_str(x).unwrap().sqrt().unwrap(); + let b = BigDecimal::from_str(y).unwrap(); + assert_eq!(a, b); + } + } + + #[test] + fn test_big_sqrt() { + use num_bigint::BigInt; + let vals = vec![ + (("2", -70), "141421356237309504880168872420969807.8569671875376948073176679737990732478462107038850387534327641573"), + (("3", -170), "17320508075688772935274463415058723669428052538103806280558069794519330169088000370811.46186757248576"), + (("9", -199), "9486832980505137995996680633298155601158665417975650480572514558377783315917714664032744325137900886"), + (("7", -200), "26457513110645905905016157536392604257102591830824501803683344592010688232302836277603928864745436110"), + (("777", -204), "2.787471972953270789531596912111625325974789615194854615319795902911796043681078997362635440358922503E+103"), + (("7", -600), "2.645751311064590590501615753639260425710259183082450180368334459201068823230283627760392886474543611E+300"), + (("2", -900), "1.414213562373095048801688724209698078569671875376948073176679737990732478462107038850387534327641573E+450"), + (("7", -999), "8.366600265340755479781720257851874893928153692986721998111915430804187725943170098308147119649515362E+499"), + (("74908163946345982392040522594123773796", -999), "2.736935584670307552030924971360722787091742391079630976117950955395149091570790266754718322365663909E+518"), + (("20", -1024), "4.472135954999579392818347337462552470881236719223051448541794490821041851275609798828828816757564550E512"), + (("3", 1025), "5.477225575051661134569697828008021339527446949979832542268944497324932771227227338008584361638706258E-513"), + ]; + for &((s, scale), e) in vals.iter() { + let expected = BigDecimal::from_str(e).unwrap(); + + let sqrt = BigDecimal::new(BigInt::from_str(s).unwrap(), scale).sqrt().unwrap(); + assert_eq!(sqrt, expected); + } + } + #[test] fn case_sqrt_3242053850483855em13() { let d: BigDecimal = "324.2053850483855".parse().unwrap(); let digitref = d.to_ref(); let (_, scale, uint) = digitref.as_parts(); - // let prec = NonZeroUsize::new(11); - // let ctx = Context::new(prec.unwrap(), RoundingMode::Up); let ctx = Context::default() .with_prec(11).unwrap() .with_rounding_mode(RoundingMode::Down); @@ -184,4 +235,24 @@ mod test { let expected: BigDecimal = "0.00002254998889653906459324292".parse().unwrap(); assert_eq!(s, expected); } + + #[cfg(property_tests)] + mod prop { + use super::*; + use proptest::*; + use num_traits::FromPrimitive; + + proptest! { + #[test] + fn sqrt_of_square_is_self(f: f64, prec in 15..50u64) { + // ignore non-normal numbers + prop_assume!(f.is_normal()); + + let n = BigDecimal::from_f64(f.abs()).unwrap().with_prec(prec); + let n_squared = n.square(); + let x = n_squared.sqrt().unwrap(); + prop_assert_eq!(x, n); + } + } + } } diff --git a/src/lib.rs b/src/lib.rs index 247b80b..fbfb953 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1965,59 +1965,6 @@ mod bigdecimal_tests { } } - #[test] - fn test_sqrt() { - let vals = vec![ - ("1e-232", "1e-116"), - ("1.00", "1"), - ("1.001", "1.000499875062460964823258287700109753027590031219780479551442971840836093890879944856933288426795152"), - ("100", "10"), - ("49", "7"), - (".25", ".5"), - ("0.0152399025", ".12345"), - ("152399025", "12345"), - (".00400", "0.06324555320336758663997787088865437067439110278650433653715009705585188877278476442688496216758600590"), - (".1", "0.3162277660168379331998893544432718533719555139325216826857504852792594438639238221344248108379300295"), - ("2", "1.414213562373095048801688724209698078569671875376948073176679737990732478462107038850387534327641573"), - ("125348", "354.0451948551201563108487193176101314241016013304294520812832530590100407318465590778759640828114535"), - ("18446744073709551616.1099511", "4294967296.000000000012799992691725492477397918722952224079252026972356303360555051219312462698703293"), - ("3.141592653589793115997963468544185161590576171875", "1.772453850905515992751519103139248439290428205003682302442979619028063165921408635567477284443197875"), - (".000000000089793115997963468544185161590576171875", "0.000009475922962855041517561783740144225422359796851494316346796373337470068631250135521161989831460407155"), - ("0.7177700109762963922745342343167413624881759290454997218753321040760896053150388903350654937434826216803814031987652326749140535150336357405672040727695124057298138872112244784753994931999476811850580200000000000000000000000000000000", "0.8472130847527653667042980517799020703921106560594525833177762276594388966885185567535692987624493813"), - ("0.01234567901234567901234567901234567901234567901234567901234567901234567901234567901234567901234567901", "0.1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"), - ("0.1108890000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000444", "0.3330000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000667"), - ]; - for &(x, y) in vals.iter() { - let a = BigDecimal::from_str(x).unwrap().sqrt().unwrap(); - let b = BigDecimal::from_str(y).unwrap(); - assert_eq!(a, b); - } - } - - #[test] - fn test_big_sqrt() { - use num_bigint::BigInt; - let vals = vec![ - (("2", -70), "141421356237309504880168872420969807.8569671875376948073176679737990732478462107038850387534327641573"), - (("3", -170), "17320508075688772935274463415058723669428052538103806280558069794519330169088000370811.46186757248576"), - (("9", -199), "9486832980505137995996680633298155601158665417975650480572514558377783315917714664032744325137900886"), - (("7", -200), "26457513110645905905016157536392604257102591830824501803683344592010688232302836277603928864745436110"), - (("777", -204), "2.787471972953270789531596912111625325974789615194854615319795902911796043681078997362635440358922503E+103"), - (("7", -600), "2.645751311064590590501615753639260425710259183082450180368334459201068823230283627760392886474543611E+300"), - (("2", -900), "1.414213562373095048801688724209698078569671875376948073176679737990732478462107038850387534327641573E+450"), - (("7", -999), "8.366600265340755479781720257851874893928153692986721998111915430804187725943170098308147119649515362E+499"), - (("74908163946345982392040522594123773796", -999), "2.736935584670307552030924971360722787091742391079630976117950955395149091570790266754718322365663909E+518"), - (("20", -1024), "4.472135954999579392818347337462552470881236719223051448541794490821041851275609798828828816757564550E512"), - (("3", 1025), "5.477225575051661134569697828008021339527446949979832542268944497324932771227227338008584361638706258E-513"), - ]; - for &((s, scale), e) in vals.iter() { - let expected = BigDecimal::from_str(e).unwrap(); - - let sqrt = BigDecimal::new(BigInt::from_str(s).unwrap(), scale).sqrt().unwrap(); - assert_eq!(sqrt, expected); - } - } - mod double { use super::*; From 1105432264de42049d65cfcfbeabfe671ea17d7f Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Wed, 11 Oct 2023 21:33:38 -0400 Subject: [PATCH 47/77] Fix clippy lints --- src/arithmetic/inverse.rs | 2 +- src/context.rs | 3 +-- src/lib.rs | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/arithmetic/inverse.rs b/src/arithmetic/inverse.rs index 6a51289..e291707 100644 --- a/src/arithmetic/inverse.rs +++ b/src/arithmetic/inverse.rs @@ -43,7 +43,7 @@ pub(crate) fn impl_inverse_uint_scale(n: &BigUint, scale: i64, ctx: &Context) -> fn make_inv_guess(bit_count: u64, scale: i64) -> BigDecimal { // scale by ln(2) - let magic_factor = 0.6931471805599453_f64; + let magic_factor = stdlib::f64::consts::LN_2; let bit_count = bit_count as f64; let scale = scale as f64; diff --git a/src/context.rs b/src/context.rs index d5601ca..c48cc84 100644 --- a/src/context.rs +++ b/src/context.rs @@ -56,8 +56,7 @@ impl Context { pub fn with_prec(&self, precision: T) -> Option { precision .to_u64() - .map(NonZeroU64::new) - .flatten() + .and_then(NonZeroU64::new) .map(|prec| { Self { precision: prec, diff --git a/src/lib.rs b/src/lib.rs index fbfb953..51949a0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -196,7 +196,7 @@ impl BigDecimal { } /// Make a BigDecimalRef of this value - pub fn to_ref<'a>(&'a self) -> BigDecimalRef<'a> { + pub fn to_ref(&self) -> BigDecimalRef<'_> { // search for "From<&'a BigDecimal> for BigDecimalRef<'a>" self.into() } From 50995104dcafc2b867c8e1d671c0deb7a7f6024a Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 14 Oct 2023 20:30:20 -0400 Subject: [PATCH 48/77] Mark BigDecimalRef::as_parts as used --- src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 51949a0..076e5a5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1210,7 +1210,6 @@ impl BigDecimalRef<'_> { } /// Split into components - #[allow(dead_code)] pub(crate) fn as_parts(&self) -> (Sign, i64, &BigUint) { (self.sign, self.scale, self.digits) } From 4a772f5346744200baf4da2084e42f9f68f97795 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 14 Oct 2023 13:01:51 -0400 Subject: [PATCH 49/77] Remove pub modifier from rounding mod --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 076e5a5..7eec7d0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -116,7 +116,7 @@ mod impl_num; mod parsing; // Routines for rounding -pub mod rounding; +mod rounding; pub use rounding::RoundingMode; // Mathematical context From 837137f073bb3d5f47d8f6197e5ef299f45f2528 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 14 Oct 2023 13:52:17 -0400 Subject: [PATCH 50/77] Impl From<&BigInt> for BigDecimalRef --- src/lib.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 7eec7d0..87ecebb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1249,6 +1249,16 @@ impl<'a> From<&'a BigDecimal> for BigDecimalRef<'a> { } } +impl<'a> From<&'a BigInt> for BigDecimalRef<'a> { + fn from(n: &'a BigInt) -> Self { + Self { + sign: n.sign(), + digits: n.magnitude(), + scale: 0, + } + } +} + impl<'a> From> for BigDecimal { fn from(n: BigDecimalRef<'a>) -> Self { n.to_owned() From ffd7988b85850882640964c5b22fa7b6519e1922 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 14 Oct 2023 13:34:40 -0400 Subject: [PATCH 51/77] Support Add & AddAssign BigDecimalRef --- src/impl_ops_add.rs | 68 ++++++++++++--------------------------------- 1 file changed, 17 insertions(+), 51 deletions(-) diff --git a/src/impl_ops_add.rs b/src/impl_ops_add.rs index 6928139..525eab1 100644 --- a/src/impl_ops_add.rs +++ b/src/impl_ops_add.rs @@ -22,24 +22,17 @@ impl Add for BigDecimal { } } -impl<'a> Add<&'a BigDecimal> for BigDecimal { +impl<'a, T: Into>> Add for BigDecimal { type Output = BigDecimal; - #[inline] - fn add(self, rhs: &'a BigDecimal) -> BigDecimal { + fn add(self, rhs: T) -> BigDecimal { let mut lhs = self; - - match lhs.scale.cmp(&rhs.scale) { - Ordering::Equal => { - lhs.int_val += &rhs.int_val; - lhs - } - Ordering::Less => lhs.take_and_scale(rhs.scale) + rhs, - Ordering::Greater => rhs.with_scale(lhs.scale) + lhs, - } + lhs += rhs.into(); + lhs } } + impl<'a> Add for &'a BigDecimal { type Output = BigDecimal; @@ -84,27 +77,6 @@ impl Add for BigDecimal { } } -impl<'a> Add<&'a BigInt> for BigDecimal { - type Output = BigDecimal; - - #[inline] - fn add(self, rhs: &BigInt) -> BigDecimal { - let mut lhs = self; - - match lhs.scale.cmp(&0) { - Ordering::Equal => { - lhs.int_val += rhs; - lhs - } - Ordering::Greater => { - lhs.int_val += rhs * ten_to_the(lhs.scale as u64); - lhs - } - Ordering::Less => lhs.take_and_scale(0) + rhs, - } - } -} - impl<'a> Add for &'a BigDecimal { type Output = BigDecimal; @@ -125,9 +97,11 @@ impl<'a, 'b> Add<&'a BigInt> for &'b BigDecimal { forward_val_assignop!(impl AddAssign for BigDecimal, add_assign); -impl<'a> AddAssign<&'a BigDecimal> for BigDecimal { +impl<'a, N: Into>> AddAssign for BigDecimal { #[inline] - fn add_assign(&mut self, rhs: &BigDecimal) { + fn add_assign(&mut self, rhs: N) { + // TODO: Replace to_owned() with efficient addition algorithm + let rhs = rhs.into().to_owned(); match self.scale.cmp(&rhs.scale) { Ordering::Less => { let scaled = self.with_scale(rhs.scale); @@ -148,23 +122,15 @@ impl<'a> AddAssign<&'a BigDecimal> for BigDecimal { impl AddAssign for BigDecimal { #[inline] fn add_assign(&mut self, rhs: BigInt) { - *self += BigDecimal::new(rhs, 0) - } -} + let sign = rhs.sign(); + let mag = rhs.magnitude(); + let rhs_ref = BigDecimalRef { + scale: 0, + sign: sign, + digits: mag.into(), + }; -impl<'a> AddAssign<&'a BigInt> for BigDecimal { - #[inline] - fn add_assign(&mut self, rhs: &BigInt) { - match self.scale.cmp(&0) { - Ordering::Equal => self.int_val += rhs, - Ordering::Greater => self.int_val += rhs * ten_to_the(self.scale as u64), - Ordering::Less => { - // *self += BigDecimal::new(rhs, 0) - self.int_val *= ten_to_the((-self.scale) as u64); - self.int_val += rhs; - self.scale = 0; - } - } + self.add_assign(rhs_ref); } } From 52d7e5ec42c7109945e4d1e4eff40df78b64edd1 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 14 Oct 2023 14:07:06 -0400 Subject: [PATCH 52/77] impl Add for BigDecimalRef --- src/impl_ops.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/impl_ops.rs b/src/impl_ops.rs index 829f631..9cac1be 100644 --- a/src/impl_ops.rs +++ b/src/impl_ops.rs @@ -23,6 +23,14 @@ macro_rules! impl_add_for_primitive { impl Add<$t> for &BigDecimal { type Output = BigDecimal; + fn add(self, rhs: $t) -> BigDecimal { + self.to_ref() + rhs + } + } + + impl Add<$t> for BigDecimalRef<'_> { + type Output = BigDecimal; + fn add(self, rhs: $t) -> BigDecimal { BigDecimal::from(rhs) + self } From bb185d912f39bf9acd7033b8970a6c3235f34c4e Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 14 Oct 2023 14:09:35 -0400 Subject: [PATCH 53/77] Reorganize impl Add/AddAssign with BigDecimalRef --- src/impl_ops_add.rs | 62 ++++++++++++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/src/impl_ops_add.rs b/src/impl_ops_add.rs index 525eab1..5bf37b0 100644 --- a/src/impl_ops_add.rs +++ b/src/impl_ops_add.rs @@ -25,15 +25,13 @@ impl Add for BigDecimal { impl<'a, T: Into>> Add for BigDecimal { type Output = BigDecimal; - fn add(self, rhs: T) -> BigDecimal { - let mut lhs = self; - lhs += rhs.into(); - lhs + fn add(mut self, rhs: T) -> BigDecimal { + self.add_assign(rhs); + self } } - -impl<'a> Add for &'a BigDecimal { +impl Add for &'_ BigDecimal { type Output = BigDecimal; #[inline] @@ -42,7 +40,7 @@ impl<'a> Add for &'a BigDecimal { } } -impl<'a, 'b> Add<&'b BigDecimal> for &'a BigDecimal { +impl<'a> Add<&'a BigDecimal> for &'_ BigDecimal { type Output = BigDecimal; #[inline] @@ -61,37 +59,55 @@ impl Add for BigDecimal { #[inline] fn add(self, rhs: BigInt) -> BigDecimal { - let mut lhs = self; + self + BigDecimal::from(rhs) + } +} - match lhs.scale.cmp(&0) { - Ordering::Equal => { - lhs.int_val += rhs; - lhs - } - Ordering::Greater => { - lhs.int_val += rhs * ten_to_the(lhs.scale as u64); - lhs - } - Ordering::Less => lhs.take_and_scale(0) + rhs, +impl Add for &'_ BigDecimal { + type Output = BigDecimal; + + #[inline] + fn add(self, rhs: BigInt) -> BigDecimal { + self.to_ref() + rhs + } +} + +impl<'a> Add<&'a BigInt> for &'_ BigDecimal { + type Output = BigDecimal; + + #[inline] + fn add(self, rhs: &BigInt) -> BigDecimal { + self.to_ref() + rhs + } +} + +impl<'a, T: Into>> Add for BigDecimalRef<'_> { + type Output = BigDecimal; + fn add(self, rhs: T) -> BigDecimal { + let rhs = rhs.into(); + if self.scale >= rhs.scale { + self.to_owned() + rhs + } else { + rhs.to_owned() + self } } } -impl<'a> Add for &'a BigDecimal { +impl Add for BigDecimalRef<'_> { type Output = BigDecimal; #[inline] fn add(self, rhs: BigInt) -> BigDecimal { - BigDecimal::new(rhs, 0) + self + self + BigDecimal::from(rhs) } } -impl<'a, 'b> Add<&'a BigInt> for &'b BigDecimal { +impl Add for BigDecimalRef<'_> { type Output = BigDecimal; #[inline] - fn add(self, rhs: &BigInt) -> BigDecimal { - self.with_scale(0) + rhs + fn add(self, rhs: BigDecimal) -> BigDecimal { + rhs + self } } From 6c592b4566c83939d64569bfaf268c81e1e8deaf Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 14 Oct 2023 13:13:21 -0400 Subject: [PATCH 54/77] Expand documentation of BigDecimalRef --- src/lib.rs | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 87ecebb..c053eba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1171,17 +1171,44 @@ impl fmt::Debug for BigDecimal { } -/// A big-decimal wrapping a borrowed (immutable) buffer of digits +/// Immutable big-decimal, referencing a borrowed buffer of digits /// /// The non-digit information like `scale` and `sign` may be changed /// on these objects, which otherwise would require cloning the full /// digit buffer in the BigDecimal. /// -/// May be transformed into full BigDecimal object using the to_owned() +/// Built from full `BigDecimal` object using the `to_ref()` method. +/// `BigDecimal` not implement `AsRef`, so we will reserve the method +/// `as_ref()` for a later time. +/// +/// May be transformed into full BigDecimal object using the `to_owned()` /// method. +/// This clones the bigdecimal digits. +/// +/// BigDecimalRef (or `Into`) should be preferred over +/// using `&BigDecimal` for library functions that need an immutable +/// reference to a bigdecimal, as it may be much more efficient. +/// +/// NOTE: Using `&BigDecimalRef` is redundant, and not recommended. +/// +/// ## Examples +/// +/// ``` +/// # use bigdecimal::*; use std::ops::Neg; +/// fn add_one<'a, N: Into>>(n: N) -> BigDecimal { +/// n.into() + 1 +/// } +/// +/// let n: BigDecimal = "123.456".parse().unwrap(); +/// +/// // call via "standard" reference (implements Into) +/// let m = add_one(&n); +/// assert_eq!(m, "124.456".parse().unwrap()); /// -/// BigDecimalRefs should be preferred over using &BigDecimal for most -/// functions that need an immutable reference to a bigdecimal. +/// // call by negating the reference (fast: no-digit cloning involved) +/// let m = add_one(n.to_ref().neg()); +/// assert_eq!(m, "-122.456".parse().unwrap()); +/// ``` /// #[derive(Clone, Copy, Debug)] pub struct BigDecimalRef<'a> { From 3ac636857457cd265fa81a43bef4705442c795dc Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 14 Oct 2023 21:20:02 -0400 Subject: [PATCH 55/77] Remove gitlab-ci Rust-1.54 jobs and make Rust-1.70 automatic --- .gitlab-ci.yml | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index bbc0d0a..d1ca397 100755 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -243,39 +243,9 @@ cargo:test-1.43: <<: *script-cargo-test -cargo:check-1.54: - stage: check - image: "akubera/rust-kcov:1.54.0-bullseye" - rules: - *rules-always-master-otherwise-manual - variables: - RUST_CACHE_KEY: "1.54" - <<: *script-cargo-check - -cargo:build-1.54: - stage: build - image: "akubera/rust-kcov:1.54.0-bullseye" - needs: - - "cargo:check-1.54" - variables: - RUST_CACHE_KEY: "1.54" - <<: *script-cargo-build - -cargo:test-1.54: - stage: test - needs: - - "cargo:build-1.54" - image: "akubera/rust-kcov:1.54.0-bullseye" - variables: - RUST_CACHE_KEY: "1.54" - <<: *script-cargo-test - - cargo:check-1.70: stage: check image: "akubera/rust-grcov:1.70.0-bullseye" - rules: - *rules-always-master-otherwise-manual variables: RUST_CACHE_KEY: "1.70" <<: *script-cargo-check From 72101874ce0d1e40295aa0d0f90168f91456b6ff Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 14 Oct 2023 21:31:49 -0400 Subject: [PATCH 56/77] Fix another typo --- src/impl_cmp.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/impl_cmp.rs b/src/impl_cmp.rs index ecef30a..32f7019 100644 --- a/src/impl_cmp.rs +++ b/src/impl_cmp.rs @@ -5,7 +5,7 @@ //! inference features at the savings of a single //! '&' character. //! -//! &BigDecimal and BigDecimalRef are comparible. +//! &BigDecimal and BigDecimalRef are comparable. //! use crate::{ From 656f3358c6e785cd3dce4e401d0cd461013e1efe Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 14 Oct 2023 21:36:43 -0400 Subject: [PATCH 57/77] Add serde ci jobs --- .gitlab-ci.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d1ca397..fbd98f0 100755 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -197,6 +197,29 @@ cargo:test:no-std: - cargo test --no-default-features --lib +cargo:build:serde: + stage: build + image: akubera/rust:stable + needs: + - cargo:check + variables: + RUST_CACHE_KEY: "stable+serde" + <<: *script-cargo-build + script: + - cargo build --features=serde --all-targets + +cargo:test:serde: + stage: test + image: akubera/rust:stable + needs: + - "cargo:build:no-std" + variables: + RUST_CACHE_KEY: "stable+serde" + <<: *script-cargo-test + script: + - cargo test --features=serde --all-targets + + cargo:build-nightly: stage: build image: rustlang/rust:nightly From a0fad26c270f0212b1b74de889143b9b3e683842 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 15 Oct 2023 10:54:55 +0900 Subject: [PATCH 58/77] Add `fractional_digit_count` method to `BigDecimal` for efficient number type determination --- src/lib.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index c053eba..6b09ce6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -201,6 +201,25 @@ impl BigDecimal { self.into() } + /// Returns the scale of the BigDecimal + /// + /// # Examples + /// + /// ``` + /// use bigdecimal::BigDecimal; + /// use std::str::FromStr; + /// + /// let a = BigDecimal::from(12345); // No fractional part + /// let b = BigDecimal::from_str("123.45").unwrap(); // Fractional part + /// + /// assert_eq!(a.fractional_digit_count(), 0); + /// assert_eq!(b.fractional_digit_count(), 2); + /// ``` + #[inline] + pub fn fractional_digit_count(&self) -> i64 { + self.scale + } + /// Creates and initializes a `BigDecimal`. /// /// Decodes using `str::from_utf8` and forwards to `BigDecimal::from_str_radix`. @@ -1466,6 +1485,21 @@ mod bigdecimal_tests { use num_bigint; use paste::paste; + #[test] + fn test_fractional_digit_count() { + // Zero value + let vals = BigDecimal::from(0); + assert_eq!(vals.fractional_digit_count(), 0); + + // Fractional part with trailing zeros + let vals = BigDecimal::from_str("1.0").unwrap(); + assert_eq!(vals.fractional_digit_count(), 1); + + // Fractional part + let vals = BigDecimal::from_str("1.23").unwrap(); + assert_eq!(vals.fractional_digit_count(), 2); + } + #[test] fn test_sum() { let vals = vec![ From cb21dc3a2fd22e767cb1ddb8546833ea28c7c29f Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 14 Oct 2023 23:08:39 -0400 Subject: [PATCH 59/77] Add fractional_digit_count to BigDecimalRef and expand tests and docs --- src/lib.rs | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6b09ce6..404587f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -201,7 +201,9 @@ impl BigDecimal { self.into() } - /// Returns the scale of the BigDecimal + /// Returns the scale of the BigDecimal, the total number of + /// digits to the right of the decimal point (including insignificant + /// leading zeros) /// /// # Examples /// @@ -211,9 +213,13 @@ impl BigDecimal { /// /// let a = BigDecimal::from(12345); // No fractional part /// let b = BigDecimal::from_str("123.45").unwrap(); // Fractional part + /// let c = BigDecimal::from_str("0.0000012345").unwrap(); // Completely fractional part + /// let d = BigDecimal::from_str("5e9").unwrap(); // Negative-fractional part /// /// assert_eq!(a.fractional_digit_count(), 0); /// assert_eq!(b.fractional_digit_count(), 2); + /// assert_eq!(c.fractional_digit_count(), 10); + /// assert_eq!(d.fractional_digit_count(), -9); /// ``` #[inline] pub fn fractional_digit_count(&self) -> i64 { @@ -1250,7 +1256,13 @@ impl BigDecimalRef<'_> { self.sign } - /// Count number of decimal digits + /// Return number of digits 'right' of the decimal point + /// (including leading zeros) + pub fn fractional_digit_count(&self) -> i64 { + self.scale + } + + /// Count total number of decimal digits pub fn count_digits(&self) -> u64 { count_decimal_digits_uint(self.digits) } @@ -1490,14 +1502,22 @@ mod bigdecimal_tests { // Zero value let vals = BigDecimal::from(0); assert_eq!(vals.fractional_digit_count(), 0); + assert_eq!(vals.to_ref().fractional_digit_count(), 0); // Fractional part with trailing zeros let vals = BigDecimal::from_str("1.0").unwrap(); assert_eq!(vals.fractional_digit_count(), 1); + assert_eq!(vals.to_ref().fractional_digit_count(), 1); // Fractional part let vals = BigDecimal::from_str("1.23").unwrap(); assert_eq!(vals.fractional_digit_count(), 2); + assert_eq!(vals.to_ref().fractional_digit_count(), 2); + + // shifted to 'left' has negative scale + let vals = BigDecimal::from_str("123e5").unwrap(); + assert_eq!(vals.fractional_digit_count(), -5); + assert_eq!(vals.to_ref().fractional_digit_count(), -5); } #[test] From 438084e2e0380c609005f1794bf0dee240c577af Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 14 Oct 2023 23:09:14 -0400 Subject: [PATCH 60/77] Add BigDecimalRef::is_zero --- src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 404587f..a617bb5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1293,6 +1293,11 @@ impl BigDecimalRef<'_> { Plus => Some(arithmetic::sqrt::impl_sqrt(uint, scale, ctx)), } } + + /// Return if the referenced decimal is zero + pub fn is_zero(&self) -> bool { + self.digits.is_zero() + } } impl<'a> From<&'a BigDecimal> for BigDecimalRef<'a> { From a61b5820d22ed1785f509baa14e9a188614d9a9a Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 14 Oct 2023 23:10:13 -0400 Subject: [PATCH 61/77] to_owned_with_scale --- src/lib.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index a617bb5..878b06c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1251,6 +1251,36 @@ impl BigDecimalRef<'_> { } } + /// Clone digits, returning BigDecimal with given scale + /// + /// ``` + /// # use bigdecimal::*; + /// + /// let n: BigDecimal = "123.45678".parse().unwrap(); + /// let r = n.to_ref(); + /// assert_eq!(r.to_owned_with_scale(5), n.clone()); + /// assert_eq!(r.to_owned_with_scale(0), "123".parse().unwrap()); + /// assert_eq!(r.to_owned_with_scale(-1), "12e1".parse().unwrap()); + /// + /// let x = r.to_owned_with_scale(8); + /// assert_eq!(&x, &n); + /// assert_eq!(x.fractional_digit_count(), 8); + /// ``` + pub fn to_owned_with_scale(&self, scale: i64) -> BigDecimal { + use stdlib::cmp::Ordering::*; + + let digits = match scale.cmp(&self.scale) { + Equal => self.digits.clone(), + Greater => self.digits * ten_to_the_uint((scale - self.scale) as u64), + Less => self.digits / ten_to_the_uint((self.scale - scale) as u64) + }; + + BigDecimal { + scale: scale, + int_val: BigInt::from_biguint(self.sign, digits), + } + } + /// Sign of decimal pub fn sign(&self) -> Sign { self.sign From 71f0d63c176143bab328637e8ba57e4957a116b3 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sun, 15 Oct 2023 00:04:41 -0400 Subject: [PATCH 62/77] Impl Sub/SubAssign for BigDecimalRef --- src/impl_ops_sub.rs | 126 ++++++++++++++++++++------------------------ 1 file changed, 58 insertions(+), 68 deletions(-) diff --git a/src/impl_ops_sub.rs b/src/impl_ops_sub.rs index 1961682..82cd9d8 100644 --- a/src/impl_ops_sub.rs +++ b/src/impl_ops_sub.rs @@ -10,6 +10,14 @@ impl Sub for BigDecimal { #[inline] fn sub(self, rhs: BigDecimal) -> BigDecimal { + if rhs.is_zero() { + return self; + } + + if self.is_zero() { + return rhs.neg(); + } + let mut lhs = self; let scale = cmp::max(lhs.scale, rhs.scale); @@ -24,21 +32,34 @@ impl Sub for BigDecimal { } } -impl<'a> Sub<&'a BigDecimal> for BigDecimal { +impl<'a, T: Into>> Sub for BigDecimal { type Output = BigDecimal; #[inline] - fn sub(self, rhs: &BigDecimal) -> BigDecimal { - let mut lhs = self; - let scale = cmp::max(lhs.scale, rhs.scale); + fn sub(mut self, rhs: T) -> BigDecimal { + let rhs = rhs.into(); + if rhs.is_zero() { + return self + } + if self.is_zero() { + self.int_val = BigInt::from_biguint(rhs.sign.neg(), rhs.digits.clone()); + self.scale = rhs.scale; + return self + } + + let mut lhs = self; match lhs.scale.cmp(&rhs.scale) { Ordering::Equal => { - lhs.int_val -= &rhs.int_val; + lhs.int_val -= BigInt::from_biguint(rhs.sign, rhs.digits.clone()); lhs } - Ordering::Less => lhs.take_and_scale(rhs.scale) - rhs, - Ordering::Greater => lhs - rhs.with_scale(scale), + Ordering::Less => { + lhs.take_and_scale(rhs.scale) - rhs.to_owned() + } + Ordering::Greater => { + lhs - rhs.to_owned_with_scale(lhs.scale) + }, } } } @@ -52,48 +73,15 @@ impl<'a> Sub for &'a BigDecimal { } } -impl<'a, 'b> Sub<&'b BigDecimal> for &'a BigDecimal { - type Output = BigDecimal; - - #[inline] - fn sub(self, rhs: &BigDecimal) -> BigDecimal { - match self.scale.cmp(&rhs.scale) { - Ordering::Greater => { - let rhs = rhs.with_scale(self.scale); - self - rhs - } - Ordering::Less => self.with_scale(rhs.scale) - rhs, - Ordering::Equal => BigDecimal::new(&self.int_val - &rhs.int_val, self.scale), - } - } -} - impl Sub for BigDecimal { type Output = BigDecimal; #[inline] fn sub(self, rhs: BigInt) -> BigDecimal { - let mut lhs = self; - - match lhs.scale.cmp(&0) { - Ordering::Equal => { - lhs.int_val -= rhs; - lhs - } - Ordering::Greater => { - lhs.int_val -= rhs * ten_to_the(lhs.scale as u64); - lhs - } - Ordering::Less => lhs.take_and_scale(0) - rhs, + if rhs.is_zero() { + return self; } - } -} -impl<'a> Sub<&'a BigInt> for BigDecimal { - type Output = BigDecimal; - - #[inline] - fn sub(self, rhs: &BigInt) -> BigDecimal { let mut lhs = self; match lhs.scale.cmp(&0) { @@ -119,31 +107,46 @@ impl<'a> Sub for &'a BigDecimal { } } -impl<'a, 'b> Sub<&'a BigInt> for &'b BigDecimal { +impl<'a, 'b, T: Into>> Sub for &'a BigDecimal { type Output = BigDecimal; #[inline] - fn sub(self, rhs: &BigInt) -> BigDecimal { - self.with_scale(0) - rhs + fn sub(self, rhs: T) -> BigDecimal { + let rhs = rhs.into(); + + match self.scale.cmp(&rhs.scale) { + Ordering::Equal => self.clone() - rhs, + Ordering::Less => self.with_scale(rhs.scale) - rhs, + Ordering::Greater => self - rhs.to_owned_with_scale(self.scale), + } } } forward_val_assignop!(impl SubAssign for BigDecimal, sub_assign); -impl<'a> SubAssign<&'a BigDecimal> for BigDecimal { +impl<'rhs, T: Into>> SubAssign for BigDecimal { #[inline] - fn sub_assign(&mut self, rhs: &BigDecimal) { + fn sub_assign(&mut self, rhs: T) { + let rhs = rhs.into(); + if rhs.is_zero() { + return; + } + if self.is_zero() { + *self = rhs.neg().to_owned(); + return; + } + match self.scale.cmp(&rhs.scale) { + Ordering::Equal => { + self.int_val -= rhs.to_owned().int_val; + } Ordering::Less => { - let lhs = self.with_scale(rhs.scale); - self.int_val = lhs.int_val - &rhs.int_val; + self.int_val *= ten_to_the((rhs.scale - self.scale) as u64); + self.int_val -= rhs.to_owned().int_val; self.scale = rhs.scale; } Ordering::Greater => { - self.int_val -= rhs.with_scale(self.scale).int_val; - } - Ordering::Equal => { - self.int_val = &self.int_val - &rhs.int_val; + *self -= rhs.to_owned_with_scale(self.scale); } } } @@ -156,21 +159,6 @@ impl SubAssign for BigDecimal { } } -impl<'a> SubAssign<&'a BigInt> for BigDecimal { - #[inline(always)] - fn sub_assign(&mut self, rhs: &BigInt) { - match self.scale.cmp(&0) { - Ordering::Equal => SubAssign::sub_assign(&mut self.int_val, rhs), - Ordering::Greater => SubAssign::sub_assign(&mut self.int_val, rhs * ten_to_the(self.scale as u64)), - Ordering::Less => { - self.int_val *= ten_to_the((-self.scale) as u64); - SubAssign::sub_assign(&mut self.int_val, rhs); - self.scale = 0; - } - } - } -} - #[cfg(test)] mod test { @@ -200,6 +188,8 @@ mod test { impl_case!(case_1234en2_1234en3: "12.34" - "1.234" => "11.106"); impl_case!(case_1234en2_n1234en3: "12.34" - "-1.234" => "13.574"); impl_case!(case_1234e6_1234en6: "1234e6" - "1234e-6" => "1233999999.998766"); + impl_case!(case_85616001e4_0: "85616001e4" - "0" => "85616001e4"); + impl_case!(case_0_520707672en5: "0" - "5207.07672" => "-520707672e-5"); #[cfg(property_tests)] mod prop { From 6eeb387691d8a6b5ce93db6b619b6465770fd6dd Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sun, 15 Oct 2023 13:42:55 -0400 Subject: [PATCH 63/77] Fix and expand impl Sub<> --- src/impl_ops_sub.rs | 240 ++++++++++++++++++++++++++++++++------------ 1 file changed, 174 insertions(+), 66 deletions(-) diff --git a/src/impl_ops_sub.rs b/src/impl_ops_sub.rs index 82cd9d8..a640063 100644 --- a/src/impl_ops_sub.rs +++ b/src/impl_ops_sub.rs @@ -1,5 +1,5 @@ //! -//! Multiplication operator trait implementation +//! Subtraction operator trait implementation //! use crate::*; @@ -19,111 +19,177 @@ impl Sub for BigDecimal { } let mut lhs = self; - let scale = cmp::max(lhs.scale, rhs.scale); - match lhs.scale.cmp(&rhs.scale) { Ordering::Equal => { lhs.int_val -= rhs.int_val; lhs } - Ordering::Less => lhs.take_and_scale(scale) - rhs, - Ordering::Greater => lhs - rhs.take_and_scale(scale), + Ordering::Less => { + lhs.take_and_scale(rhs.scale) - rhs + } + Ordering::Greater => { + let rhs = rhs.take_and_scale(lhs.scale); + lhs - rhs + }, } } } -impl<'a, T: Into>> Sub for BigDecimal { +impl Sub for &'_ BigDecimal { type Output = BigDecimal; #[inline] + fn sub(self, rhs: BigDecimal) -> BigDecimal { + self.to_ref() - rhs + } +} + +impl Sub for BigDecimalRef<'_> { + type Output = BigDecimal; + + #[inline] + fn sub(self, rhs: BigDecimal) -> BigDecimal { + (rhs - self).neg() + } +} + +impl<'a, T: Into>> Sub for BigDecimal { + type Output = BigDecimal; + fn sub(mut self, rhs: T) -> BigDecimal { - let rhs = rhs.into(); - if rhs.is_zero() { - return self - } + self.sub_assign(rhs); + self + } +} - if self.is_zero() { - self.int_val = BigInt::from_biguint(rhs.sign.neg(), rhs.digits.clone()); - self.scale = rhs.scale; - return self - } +impl<'a, T: Into>> Sub for &'_ BigDecimal { + type Output = BigDecimal; - let mut lhs = self; - match lhs.scale.cmp(&rhs.scale) { + fn sub(self, rhs: T) -> BigDecimal { + let rhs = rhs.into(); + + match self.scale.cmp(&rhs.scale) { Ordering::Equal => { - lhs.int_val -= BigInt::from_biguint(rhs.sign, rhs.digits.clone()); - lhs + self.clone() - rhs } Ordering::Less => { - lhs.take_and_scale(rhs.scale) - rhs.to_owned() + self.with_scale(rhs.scale) - rhs } Ordering::Greater => { - lhs - rhs.to_owned_with_scale(lhs.scale) - }, + self - rhs.to_owned_with_scale(self.scale) + } } } } -impl<'a> Sub for &'a BigDecimal { +impl<'a, T: Into>> Sub for BigDecimalRef<'_> { type Output = BigDecimal; - #[inline] - fn sub(self, rhs: BigDecimal) -> BigDecimal { - -(rhs - self) + fn sub(self, rhs: T) -> BigDecimal { + let rhs = rhs.into(); + + match self.scale.cmp(&rhs.scale) { + Ordering::Equal => self.clone() - rhs, + Ordering::Less => self.to_owned_with_scale(rhs.scale) - rhs, + Ordering::Greater => self - rhs.to_owned_with_scale(self.scale), + } } } impl Sub for BigDecimal { type Output = BigDecimal; + fn sub(mut self, rhs: BigInt) -> BigDecimal { + self.sub_assign(rhs); + self + } +} + + +impl Sub for &'_ BigDecimal { + type Output = BigDecimal; + #[inline] fn sub(self, rhs: BigInt) -> BigDecimal { - if rhs.is_zero() { - return self; - } + self.to_ref() - rhs + } +} - let mut lhs = self; +impl Sub for BigDecimalRef<'_> { + type Output = BigDecimal; - match lhs.scale.cmp(&0) { - Ordering::Equal => { - lhs.int_val -= rhs; - lhs - } - Ordering::Greater => { - lhs.int_val -= rhs * ten_to_the(lhs.scale as u64); - lhs - } - Ordering::Less => lhs.take_and_scale(0) - rhs, - } + #[inline] + fn sub(self, rhs: BigInt) -> BigDecimal { + self - BigDecimal::from(rhs) } } -impl<'a> Sub for &'a BigDecimal { +impl<'a> Sub for BigInt { type Output = BigDecimal; #[inline] - fn sub(self, rhs: BigInt) -> BigDecimal { - BigDecimal::new(rhs, 0) - self + fn sub(self, rhs: BigDecimal) -> BigDecimal { + (rhs - self).neg() } } -impl<'a, 'b, T: Into>> Sub for &'a BigDecimal { +impl<'a> Sub for &BigInt { type Output = BigDecimal; #[inline] - fn sub(self, rhs: T) -> BigDecimal { - let rhs = rhs.into(); + fn sub(self, rhs: BigDecimal) -> BigDecimal { + (rhs - self).neg() + } +} + +impl<'a> Sub> for BigInt { + type Output = BigDecimal; + + #[inline] + fn sub(self, rhs: BigDecimalRef<'a>) -> BigDecimal { + (rhs - &self).neg() + } +} + + +impl<'a> Sub> for &BigInt { + type Output = BigDecimal; + #[inline] + fn sub(self, rhs: BigDecimalRef<'a>) -> BigDecimal { + (rhs - self).neg() + } +} + + +impl SubAssign for BigDecimal { + #[inline] + fn sub_assign(&mut self, rhs: BigDecimal) { + if rhs.is_zero() { + return; + } + if self.is_zero() { + *self = rhs.neg(); + return; + } match self.scale.cmp(&rhs.scale) { - Ordering::Equal => self.clone() - rhs, - Ordering::Less => self.with_scale(rhs.scale) - rhs, - Ordering::Greater => self - rhs.to_owned_with_scale(self.scale), + Ordering::Equal => { + self.int_val -= rhs.int_val; + } + Ordering::Less => { + self.int_val *= ten_to_the((rhs.scale - self.scale) as u64); + self.int_val -= rhs.int_val; + self.scale = rhs.scale; + } + Ordering::Greater => { + let mut rhs_int_val = rhs.int_val; + rhs_int_val *= ten_to_the((self.scale - rhs.scale) as u64); + self.int_val -= rhs_int_val; + } } } } -forward_val_assignop!(impl SubAssign for BigDecimal, sub_assign); - impl<'rhs, T: Into>> SubAssign for BigDecimal { #[inline] fn sub_assign(&mut self, rhs: T) { @@ -169,18 +235,54 @@ mod test { ($name:ident: $a:literal - $b:literal => $c:literal ) => { #[test] fn $name() { - let mut a: BigDecimal = $a.parse().unwrap(); + let a: BigDecimal = $a.parse().unwrap(); let b: BigDecimal = $b.parse().unwrap(); let c: BigDecimal = $c.parse().unwrap(); - assert_eq!(a.clone() - b.clone(), c); + assert_eq!(c, a.clone() - b.clone()); + + assert_eq!(c, a.clone() - &b); + assert_eq!(c, &a - b.clone()); + assert_eq!(c, &a - &b); - assert_eq!(a.clone() - &b, c); - assert_eq!(&a - b.clone(), c); - assert_eq!(&a - &b, c); + assert_eq!(c, a.to_ref() - &b); + assert_eq!(c, &a - b.to_ref()); + assert_eq!(c, a.to_ref() - b.to_ref()); - a -= b; - assert_eq!(a, c); + let mut n = a.clone(); + n -= b.to_ref(); + assert_eq!(n, c); + + let mut n = a.clone(); + n -= &b; + assert_eq!(n, c); + + let mut n = a.clone(); + n -= b.clone(); + assert_eq!(n, c); + + let mut n = a.clone(); + (&mut n).sub_assign(b.clone()); + assert_eq!(n, c); + } + }; + ($name:ident: $a:literal - (int) $b:literal => $c:literal ) => { + #[test] + fn $name() { + let a: BigDecimal = $a.parse().unwrap(); + let b: BigInt = $b.parse().unwrap(); + let expected: BigDecimal = $c.parse().unwrap(); + + assert_eq!(expected, a.clone() - b.clone()); + assert_eq!(expected, a.clone() - &b); + assert_eq!(expected, &a - &b); + assert_eq!(expected, &a - b.clone()); + assert_eq!(expected, a.to_ref() - &b); + + let expected_neg = expected.clone().neg(); + assert_eq!(expected_neg, b.clone() - a.clone()); + assert_eq!(expected_neg, &b - a.to_ref()); + assert_eq!(expected_neg, &b - a.clone()); } }; } @@ -188,8 +290,13 @@ mod test { impl_case!(case_1234en2_1234en3: "12.34" - "1.234" => "11.106"); impl_case!(case_1234en2_n1234en3: "12.34" - "-1.234" => "13.574"); impl_case!(case_1234e6_1234en6: "1234e6" - "1234e-6" => "1233999999.998766"); + impl_case!(case_1234en6_1234e6: "1234e-6" - "1234e6" => "-1233999999.998766"); + impl_case!(case_712911676en6_4856259269250829: "712911676e-6" - "4856259269250829" => "-4856259269250116.088324"); impl_case!(case_85616001e4_0: "85616001e4" - "0" => "85616001e4"); impl_case!(case_0_520707672en5: "0" - "5207.07672" => "-520707672e-5"); + impl_case!(case_99291289e5_int0: "99291289e5" - (int)"0" => "99291289e5"); + impl_case!(case_7051277471570131en16_int1: "0.7051277471570131" - (int)"1" => "-0.2948722528429869"); + impl_case!(case_4068603022763836en8_intneg10: "40686030.22763836" - (int)"-10" => "40686040.22763836"); #[cfg(property_tests)] mod prop { @@ -203,21 +310,22 @@ mod test { // ignore non-normal numbers prop_assume!(f.is_normal()); prop_assume!(g.is_normal()); + prop_assume!((f.log10() - g.log10()).abs() < 100_000); let a = BigDecimal::from_f32(f).unwrap(); let b = BigDecimal::from_f32(g).unwrap(); - let own_plus_ref = a.clone() + &b; - let ref_plus_own = &a + b.clone(); + let own_minus_ref = a.clone() - &b; + let ref_minus_own = &a - b.clone(); let mut c = a.clone(); - c += &b; + c -= &b; let mut d = a.clone(); - d += b; + d -= b; - prop_assert_eq!(&own_plus_ref, &ref_plus_own); - prop_assert_eq!(&c, &ref_plus_own); - prop_assert_eq!(&d, &ref_plus_own); + prop_assert_eq!(&own_minus_ref, &ref_minus_own); + prop_assert_eq!(&c, &ref_minus_own); + prop_assert_eq!(&d, &ref_minus_own); } #[test] From 1a15987c2cdb926d396cd46c67f819c0e44b3d22 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sun, 15 Oct 2023 14:50:39 -0400 Subject: [PATCH 64/77] Reorganize and expand impl_ops_add.rs --- src/impl_ops_add.rs | 208 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 177 insertions(+), 31 deletions(-) diff --git a/src/impl_ops_add.rs b/src/impl_ops_add.rs index 5bf37b0..4a02747 100644 --- a/src/impl_ops_add.rs +++ b/src/impl_ops_add.rs @@ -9,8 +9,14 @@ impl Add for BigDecimal { #[inline] fn add(self, rhs: BigDecimal) -> BigDecimal { - let mut lhs = self; + if rhs.is_zero() { + return self; + } + if self.is_zero() { + return rhs; + } + let mut lhs = self; match lhs.scale.cmp(&rhs.scale) { Ordering::Equal => { lhs.int_val += rhs.int_val; @@ -31,35 +37,41 @@ impl<'a, T: Into>> Add for BigDecimal { } } -impl Add for &'_ BigDecimal { +impl Add for BigDecimal { type Output = BigDecimal; #[inline] - fn add(self, rhs: BigDecimal) -> BigDecimal { - rhs + self + fn add(self, rhs: BigInt) -> BigDecimal { + self + BigDecimal::from(rhs) } } -impl<'a> Add<&'a BigDecimal> for &'_ BigDecimal { + + +impl Add for &'_ BigDecimal { type Output = BigDecimal; #[inline] - fn add(self, rhs: &BigDecimal) -> BigDecimal { - let lhs = self; - match self.scale.cmp(&rhs.scale) { - Ordering::Less => lhs.with_scale(rhs.scale) + rhs, - Ordering::Greater => rhs.with_scale(lhs.scale) + lhs, - Ordering::Equal => BigDecimal::new(lhs.int_val.clone() + &rhs.int_val, lhs.scale), - } + fn add(self, rhs: BigDecimal) -> BigDecimal { + rhs + self } } -impl Add for BigDecimal { +impl<'a, T: Into>> Add for &'_ BigDecimal { type Output = BigDecimal; - - #[inline] - fn add(self, rhs: BigInt) -> BigDecimal { - self + BigDecimal::from(rhs) + fn add(self, rhs: T) -> BigDecimal { + let rhs = rhs.into(); + if rhs.is_zero() { + return self.clone(); + } + if self.is_zero() { + return rhs.to_owned(); + } + if self.scale >= rhs.scale { + self.to_owned() + rhs + } else { + rhs.to_owned() + self + } } } @@ -72,12 +84,13 @@ impl Add for &'_ BigDecimal { } } -impl<'a> Add<&'a BigInt> for &'_ BigDecimal { + +impl Add for BigDecimalRef<'_> { type Output = BigDecimal; #[inline] - fn add(self, rhs: &BigInt) -> BigDecimal { - self.to_ref() + rhs + fn add(self, rhs: BigDecimal) -> BigDecimal { + rhs + self } } @@ -102,7 +115,34 @@ impl Add for BigDecimalRef<'_> { } } -impl Add for BigDecimalRef<'_> { + +impl Add for BigInt { + type Output = BigDecimal; + + #[inline] + fn add(self, rhs: BigDecimal) -> BigDecimal { + rhs + self + } +} + +impl<'a> Add<&'a BigDecimal> for BigInt { + type Output = BigDecimal; + + fn add(self, rhs: &BigDecimal) -> BigDecimal { + rhs.to_ref().add(self) + } +} + +impl<'a> Add> for BigInt { + type Output = BigDecimal; + + fn add(self, rhs: BigDecimalRef<'_>) -> BigDecimal { + rhs.add(self) + } +} + + +impl Add for &BigInt { type Output = BigDecimal; #[inline] @@ -111,7 +151,37 @@ impl Add for BigDecimalRef<'_> { } } -forward_val_assignop!(impl AddAssign for BigDecimal, add_assign); +impl<'a> Add<&'a BigDecimal> for &BigInt { + type Output = BigDecimal; + + #[inline] + fn add(self, rhs: &BigDecimal) -> BigDecimal { + rhs + self + } +} + +impl<'a> Add> for &BigInt { + type Output = BigDecimal; + + #[inline] + fn add(self, rhs: BigDecimalRef<'_>) -> BigDecimal { + rhs + self + } +} + + +impl AddAssign for BigDecimal { + fn add_assign(&mut self, rhs: BigDecimal) { + if rhs.is_zero() { + return; + } + if self.is_zero() { + *self = rhs; + return; + } + self.add_assign(rhs.to_ref()); + } +} impl<'a, N: Into>> AddAssign for BigDecimal { #[inline] @@ -157,21 +227,92 @@ mod test { use paste::paste; macro_rules! impl_case { - ($name:ident: $a:literal + $b:literal => $c:literal ) => { + ( $name:ident: $a:literal + $b:literal => $c:literal ) => { #[test] fn $name() { - let mut a: BigDecimal = $a.parse().unwrap(); + let a: BigDecimal = $a.parse().unwrap(); let b: BigDecimal = $b.parse().unwrap(); let c: BigDecimal = $c.parse().unwrap(); - assert_eq!(a.clone() + b.clone(), c); + assert_eq!(c, a.clone() + b.clone()); + assert_eq!(c, a.clone() + b.to_ref()); + assert_eq!(c, a.clone() + &b); + + assert_eq!(c, &a + b.clone()); + assert_eq!(c, &a + b.to_ref()); + assert_eq!(c, &a + &b); + + assert_eq!(c, a.to_ref() + b.clone()); + assert_eq!(c, a.to_ref() + b.to_ref()); + assert_eq!(c, a.to_ref() + &b); + + // Reversed + + assert_eq!(c, b.clone() + a.clone()); + assert_eq!(c, b.clone() + a.to_ref()); + assert_eq!(c, b.clone() + &a); + + assert_eq!(c, &b + a.clone()); + assert_eq!(c, &b + a.to_ref()); + assert_eq!(c, &b + &a); + + assert_eq!(c, b.to_ref() + a.clone()); + assert_eq!(c, b.to_ref() + a.to_ref()); + assert_eq!(c, b.to_ref() + &a); + + let mut n = a.clone(); + n += b.clone(); + assert_eq!(c, n); + + let mut n = a.clone(); + n += &b; + assert_eq!(c, n); + + let mut n = a.clone(); + n += b.to_ref(); + assert_eq!(c, n); - assert_eq!(a.clone() + &b, c); - assert_eq!(&a + b.clone(), c); - assert_eq!(&a + &b, c); + let mut n = b.clone(); + n += a.clone(); + assert_eq!(c, n); - a += b; - assert_eq!(a, c); + let mut n = b.clone(); + n += &a; + assert_eq!(c, n); + + let mut n = b.clone(); + n += a.to_ref(); + assert_eq!(c, n); + } + }; + ( $name:ident: $a:literal + (int) $b:literal => $c:literal ) => { + #[test] + fn $name() { + let a: BigDecimal = $a.parse().unwrap(); + let b: BigInt = $b.parse().unwrap(); + let c: BigDecimal = $c.parse().unwrap(); + + assert_eq!(c, a.clone() + b.clone()); + assert_eq!(c, a.clone() + &b); + assert_eq!(c, &a + &b); + assert_eq!(c, &a + b.clone()); + assert_eq!(c, a.to_ref() + &b); + + assert_eq!(c, b.clone() + a.clone()); + assert_eq!(c, b.clone() + a.to_ref()); + assert_eq!(c, b.clone() + &a); + + assert_eq!(c, &b + a.clone()); + assert_eq!(c, &b + a.to_ref()); + assert_eq!(c, &b + &a); + + let mut n = a.clone(); + n += b.clone(); + assert_eq!(c, n); + + let mut n = a.clone(); + n += &b; + assert_eq!(c, n); } }; } @@ -182,7 +323,12 @@ mod test { impl_case!(case_1234e6_1234en6: "1234e6" + "1234e-6" => "1234000000.001234"); impl_case!(case_1234en6_1234e6: "1234e6" + "1234e-6" => "1234000000.001234"); impl_case!(case_18446744073709551616_1: "18446744073709551616.0" + "1" => "18446744073709551617"); - impl_case!(case_184467440737e3380_1: "184467440737e3380" + "0" => "184467440737e3380"); + impl_case!(case_184467440737e3380_0: "184467440737e3380" + "0" => "184467440737e3380"); + impl_case!(case_0_776en1: "0" + "77.6" => "77.6"); + + impl_case!(case_80802295e5_int0: "80802295e5" + (int)"0" => "80802295e5"); + impl_case!(case_239200en4_intneg101: "23.9200" + (int)"-101" => "-77.0800"); + impl_case!(case_46636423395767125en15_int0: "46.636423395767125" + (int)"123" => "169.636423395767125"); #[cfg(property_tests)] From 7df7febb6f4a257004bedd8f8e80a7a50cfcd32e Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sun, 15 Oct 2023 14:51:27 -0400 Subject: [PATCH 65/77] Remove impl From for BigDecimal --- src/lib.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 878b06c..2d9bf82 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1352,12 +1352,6 @@ impl<'a> From<&'a BigInt> for BigDecimalRef<'a> { } } -impl<'a> From> for BigDecimal { - fn from(n: BigDecimalRef<'a>) -> Self { - n.to_owned() - } -} - /// Tools to help serializing/deserializing `BigDecimal`s #[cfg(feature = "serde")] From 61d1c631bd34b30f25274fe42c93c631bb41a4fd Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sun, 15 Oct 2023 14:51:38 -0400 Subject: [PATCH 66/77] Revert "Remove pub modifier from rounding mod" This reverts commit 4a772f5346744200baf4da2084e42f9f68f97795. --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 2d9bf82..e461a6a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -116,7 +116,7 @@ mod impl_num; mod parsing; // Routines for rounding -mod rounding; +pub mod rounding; pub use rounding::RoundingMode; // Mathematical context From 64885ac2897735c463bcc41738de068e8737dc6f Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sun, 15 Oct 2023 14:59:47 -0400 Subject: [PATCH 67/77] Add borrow to stdlib and use ToOwned in impl_ops_add --- src/impl_ops_add.rs | 1 + src/with_std.rs | 1 + src/without_std.rs | 1 + 3 files changed, 3 insertions(+) diff --git a/src/impl_ops_add.rs b/src/impl_ops_add.rs index 4a02747..f3cba79 100644 --- a/src/impl_ops_add.rs +++ b/src/impl_ops_add.rs @@ -2,6 +2,7 @@ //! use super::*; +use stdlib::borrow::ToOwned; impl Add for BigDecimal { diff --git a/src/with_std.rs b/src/with_std.rs index 4ae185b..ace3082 100644 --- a/src/with_std.rs +++ b/src/with_std.rs @@ -25,4 +25,5 @@ mod stdlib { pub use std::collections::hash_map::DefaultHasher; pub use std::vec::Vec; + pub use std::borrow; } diff --git a/src/without_std.rs b/src/without_std.rs index 4deb9bf..deac488 100644 --- a/src/without_std.rs +++ b/src/without_std.rs @@ -33,6 +33,7 @@ mod stdlib { #[cfg(test)] pub use siphasher::sip::SipHasher as DefaultHasher; + pub use alloc::borrow; pub use alloc::string; pub use alloc::vec::Vec; } From b9395c88cf86d5b0e7a961fa99aa662140eba8d6 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sun, 15 Oct 2023 15:03:37 -0400 Subject: [PATCH 68/77] Fix cargo:test:serde job dependency in gitlab-ci --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fbd98f0..d67a2f9 100755 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -212,7 +212,7 @@ cargo:test:serde: stage: test image: akubera/rust:stable needs: - - "cargo:build:no-std" + - "cargo:build:serde" variables: RUST_CACHE_KEY: "stable+serde" <<: *script-cargo-test From 942ff2276a497bd29d4f08c26862b22e1991adf7 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sun, 15 Oct 2023 15:08:00 -0400 Subject: [PATCH 69/77] Remove unnecessary property-test scale assumption --- src/impl_ops_sub.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/impl_ops_sub.rs b/src/impl_ops_sub.rs index a640063..619bd4f 100644 --- a/src/impl_ops_sub.rs +++ b/src/impl_ops_sub.rs @@ -310,7 +310,6 @@ mod test { // ignore non-normal numbers prop_assume!(f.is_normal()); prop_assume!(g.is_normal()); - prop_assume!((f.log10() - g.log10()).abs() < 100_000); let a = BigDecimal::from_f32(f).unwrap(); let b = BigDecimal::from_f32(g).unwrap(); From e72a677b1a852d9307f4663d557b70077ad105d6 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sun, 15 Oct 2023 22:51:57 -0400 Subject: [PATCH 70/77] Support subnormal floating point values --- src/parsing.rs | 69 +++++++++++++++++++++++++++-- src/parsing.tests.parse_from_f32.rs | 6 +++ src/parsing.tests.parse_from_f64.rs | 2 + 3 files changed, 73 insertions(+), 4 deletions(-) diff --git a/src/parsing.rs b/src/parsing.rs index 8bd917b..83d5ed6 100644 --- a/src/parsing.rs +++ b/src/parsing.rs @@ -17,7 +17,7 @@ pub(crate) fn try_parse_from_f32(n: f32) -> Result Err(ParseBigDecimalError::Other("NAN".into())), Infinite => Err(ParseBigDecimalError::Other("Infinite".into())), - Subnormal => Err(ParseBigDecimalError::Other("Subnormal".into())), + Subnormal => Ok(parse_from_f32_subnormal(n)), Normal | Zero => Ok(parse_from_f32(n)), } } @@ -49,9 +49,10 @@ fn split_f32_into_parts(f: f32) -> (u32, i64, Sign) { /// Create bigdecimal from f32 /// -/// Non "normal" values is undefined behavior -/// pub(crate) fn parse_from_f32(n: f32) -> BigDecimal { + if n.is_subnormal() { + return parse_from_f32_subnormal(n); + } let bits = n.to_bits(); if (bits << 1) == 0 { @@ -94,6 +95,30 @@ pub(crate) fn parse_from_f32(n: f32) -> BigDecimal { } } +/// Create bigdecimal from subnormal f32 +pub(crate) fn parse_from_f32_subnormal(n: f32) -> BigDecimal { + debug_assert!(n.is_subnormal()); + let bits = n.to_bits(); + + let sign_bit = bits >> 31; + debug_assert_eq!(bits >> 24, sign_bit << 7); + + let frac = bits - (sign_bit << 31); + + // 5^149 = 5^126 + 5^23 (f32-bit-bias=126, fraction-bits=23) + let five_to_149 = BigUint::from_slice(&[ + 1466336501, 2126633373, 2856417274, 1232167559, 2512314040, 1644054862, + 3843013918, 3873995871, 858643596, 3706384338, 65604258 + ]); + + let sign = if sign_bit == 0 { Sign::Plus } else { Sign::Minus }; + let magnitude = BigUint::from(frac) * five_to_149; + let scale = 149; + let result = BigDecimal::new(BigInt::from_biguint(sign, magnitude), scale); + return result; +} + + #[cfg(test)] #[allow(non_snake_case)] mod test_parse_from_f32 { @@ -112,7 +137,7 @@ pub(crate) fn try_parse_from_f64(n: f64) -> Result Err(ParseBigDecimalError::Other("NAN".into())), Infinite => Err(ParseBigDecimalError::Other("Infinite".into())), - Subnormal => Err(ParseBigDecimalError::Other("Subnormal".into())), + Subnormal => Ok(parse_from_f64_subnormal(n)), Normal | Zero => Ok(parse_from_f64(n)), } } @@ -141,12 +166,48 @@ fn split_f64_into_parts(f: f64) -> (u64, i64, Sign) { (frac, pow, sign) } +/// Create bigdecimal from subnormal f64 +pub(crate) fn parse_from_f64_subnormal(n: f64) -> BigDecimal { + debug_assert!(n.is_subnormal()); + let bits = n.to_bits(); + + let sign_bit = bits >> 63; + debug_assert_eq!(bits >> 52, sign_bit << 11); + + // 5^1074 = 5^1022 + 5^52 (f64-bit-bias=1022, fraction-bits=52) + let five_to_1074 = BigUint::from_slice(&[ + 2993937753, 2678407619, 3969251600, 2340035423, 635686544, 3544357150, 2618749834, + 3195461310, 2593678749, 4014721034, 2512738537, 1379014958, 2606506302, 1209795638, + 3422246832, 2235398534, 2765471138, 3453720203, 3699786234, 1752628667, 3832472493, + 2479745915, 4210941784, 2088904316, 4137646701, 3840319652, 3815898978, 2202136831, + 1022273801, 1470939580, 2032173740, 4063736241, 2069243191, 4077145663, 4033014231, + 1920904652, 4195885152, 3551517817, 4246423481, 2447790869, 1797774111, 11284306, + 195273359, 3811183395, 4065514955, 3382133286, 1078447835, 2100087074, 3915378083, + 1127077286, 1409634978, 2331452623, 1301118814, 3692061923, 2506161869, 4270519152, + 1066095370, 212429084, 3729063602, 3175008277, 2075072468, 2136773221, 4247151843, + 2395660055, 449096848, 2439918400, 1564416362, 3638689409, 3054795416, 1803373736, + 1506581328, 2791252870, 3391180271, 1768177410, 3891987426, 3655546435, 3881223940, + 903390128 + ]); + + let frac = bits - (sign_bit << 63); + + let sign = if sign_bit == 0 { Sign::Plus } else { Sign::Minus }; + let magnitude = BigUint::from(frac) * five_to_1074; + let scale = 1074; + + return BigDecimal::new(BigInt::from_biguint(sign, magnitude), scale); +} /// Create bigdecimal from f64 /// /// Non "normal" values is undefined behavior /// pub(crate) fn parse_from_f64(n: f64) -> BigDecimal { + if n.is_subnormal() { + return parse_from_f64_subnormal(n); + } + let bits = n.to_bits(); // shift right by 1 bit to handle -0.0 diff --git a/src/parsing.tests.parse_from_f32.rs b/src/parsing.tests.parse_from_f32.rs index ab9fd29..7c67b9f 100644 --- a/src/parsing.tests.parse_from_f32.rs +++ b/src/parsing.tests.parse_from_f32.rs @@ -58,6 +58,12 @@ impl_test!(_4294967295 : 4294967295. == "4294967296"); impl_test!(_158456325029e18 : 1.58456325029e+29 == "158456325028528675187087900672"); + +impl_test!(_1_40129846432e_45 : 1.40129846432e-45 == "1.40129846432481707092372958328991613128026194187651577175706828388979108268586060148663818836212158203125E-45"); +impl_test!(_1_e42 : 1e-42 == "1.0005271035279193886395429224690001177341070264998322610345467546973108330377044694614596664905548095703125e-42"); +impl_test!(_3_92E_n39 : 3.92E-39 == "3.91999933059456489828739575494312783522406115751507460249208160269472102366083987590172910131514072418212890625E-39"); +impl_test!(_2_81341650018752E_n308 : 2.81341650018752E-308 == "0"); + #[test] fn case_f32_min() { let n = f32::MIN; diff --git a/src/parsing.tests.parse_from_f64.rs b/src/parsing.tests.parse_from_f64.rs index 5f8c195..d3b9aef 100644 --- a/src/parsing.tests.parse_from_f64.rs +++ b/src/parsing.tests.parse_from_f64.rs @@ -50,6 +50,8 @@ impl_test!(_50 : 50. == "50"); impl_test!(_nanbits : bits:0b_0_11111111111_1000000000000000000000000000000000000000000000000001 => "269653970229347426076201969312749943170150807578117307259170330445749843759196293443300553362892619730839480672521111823337121537071529813188030913831084401350087805833926634314566788423582671529934053315387252306324360914392174188827078768228648633522131134987762597502339006422840407304422939101316534763520"); impl_test!(_3105036184601418e246 : bits:0b_0_11100000000_0000000000000000000000000000000000000000000000000000 => "3105036184601417870297958976925005110513772034233393222278104076052101905372753772661756817657292955900975461394262146412343160088229628782888574550082362278408909952041699811100530571263196889650525998387432937501785693707632115712"); +impl_test!(_2_81341650018752E_308 : 2.81341650018752E-308 == "2.8134165001875198278759275525943498067505063001967969175506480744152639496835355462897889950138699429916690515722729976876607247658891051736045520063301219592298855232146428654590713004216312194773871772185068366206180596731958890086634117134422695105490626598276746331472433159429067991016548063113298957324839879447939977012897422163463450947345510093578791948321798481101505330952230105511530048812659083481787407026258844307461890753626327683153826358878159001221539330872743255707112001100520519610144879206546597846231715071742093092641158571855689231930930474890818690333095288369471228217443460522531282790309374378111440076317827545086535792316428407651758951233693496387904508572484340169054222573303301594335791590596740352481219815672375261783599853515625E-308"); + #[test] fn case_f64_min() { From de1e59f1194740149312d3c25e236860b98d455a Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Mon, 16 Oct 2023 00:08:31 -0400 Subject: [PATCH 71/77] Reimplement make_inv_guess --- src/arithmetic/inverse.rs | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/arithmetic/inverse.rs b/src/arithmetic/inverse.rs index e291707..429971e 100644 --- a/src/arithmetic/inverse.rs +++ b/src/arithmetic/inverse.rs @@ -41,23 +41,33 @@ pub(crate) fn impl_inverse_uint_scale(n: &BigUint, scale: i64, ctx: &Context) -> return result; } + +/// guess inverse based on the number of bits in the integer and decimal's scale fn make_inv_guess(bit_count: u64, scale: i64) -> BigDecimal { // scale by ln(2) let magic_factor = stdlib::f64::consts::LN_2; let bit_count = bit_count as f64; - let scale = scale as f64; + let initial_guess = magic_factor * 2f64.powf(-bit_count); + if initial_guess.is_finite() && initial_guess != 0.0 { + if let Ok(mut result) = BigDecimal::try_from(initial_guess) { + result.scale -= scale; + return result; + } + } - let initial_guess = scale * LOG2_10 - bit_count; - let res = magic_factor * exp2(initial_guess); + // backup guess for out-of-range integers - match BigDecimal::try_from(res) { - Ok(res) => res, - Err(_) => { - // can't guess with float - just guess magnitude - let scale = (bit_count / LOG2_10 + scale).round() as i64; - BigDecimal::new(BigInt::from(1), -scale) - } + let approx_scale = bit_count * stdlib::f64::consts::LOG10_2; + let approx_scale_int = approx_scale.trunc(); + let approx_scale_frac = approx_scale - approx_scale_int; + + let recip = 10f64.powf(-approx_scale_frac); + let mut res = BigDecimal::from_f32((magic_factor * recip) as f32).unwrap(); + res.scale += approx_scale_int as i64; + res.scale -= scale; + return res; +} } } From 9673b530f881818f338863f707418f5425d7cb14 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Mon, 16 Oct 2023 00:09:31 -0400 Subject: [PATCH 72/77] Add tests for make_inv_guess --- src/arithmetic/inverse.rs | 79 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/src/arithmetic/inverse.rs b/src/arithmetic/inverse.rs index 429971e..b209f07 100644 --- a/src/arithmetic/inverse.rs +++ b/src/arithmetic/inverse.rs @@ -17,6 +17,7 @@ pub(crate) fn impl_inverse_uint_scale(n: &BigUint, scale: i64, ctx: &Context) -> // calculate first iteration let mut running_result = next_iteration(guess); + debug_assert!(!running_result.is_zero(), "Zero detected in inverse calculation of {}e{}", n, -scale); let mut prev_result = BigDecimal::one(); let mut result = BigDecimal::zero(); @@ -68,15 +69,91 @@ fn make_inv_guess(bit_count: u64, scale: i64) -> BigDecimal { res.scale -= scale; return res; } + + +#[cfg(test)] +mod test_make_inv_guess { + use super::*; + use paste::paste; + + macro_rules! impl_case { + ( $bin_count:literal, -$scale:literal => $expected:literal ) => { + paste! { impl_case!( [< case_ $bin_count _n $scale >]: $bin_count, -$scale => $expected); } + }; + ( $bin_count:literal, $scale:literal => $expected:literal ) => { + paste! { impl_case!( [< case_ $bin_count _ $scale >]: $bin_count, $scale => $expected); } + }; + ( $name:ident: $bin_count:expr, $scale:expr => $expected:literal ) => { + impl_case!($name: $bin_count, $scale, prec=5 => $expected); + }; + ( $name:ident: $bin_count:expr, $scale:expr, prec=$prec:literal => $expected:literal ) => { + #[test] + fn $name() { + let guess = make_inv_guess($bin_count, $scale); + let expected: BigDecimal = $expected.parse().unwrap(); + assert_eq!(guess.with_prec($prec), expected.with_prec($prec)); + } + }; } -} + impl_case!(0, 0 => "0.69315"); + impl_case!(1, 0 => "0.34657"); + impl_case!(2, 0 => "0.17329"); + impl_case!(2, 1 => "1.7329"); + + // 1 / (2^3 * 10^5) ~ + impl_case!(3, -5 => "8.6643e-07"); + + // 2^-20 + impl_case!(20, 0 => "6.6104e-07"); + impl_case!(20, -900 => "6.6104E-907"); + impl_case!(20, 800 => "6.6104E+793"); + + impl_case!(40, 10000 => "6.3041E+9987"); + + impl_case!(70, -5 => "5.8712e-27"); + impl_case!(70, 5 => "5.8712e-17"); + impl_case!(70, 50 => "5.8712e+28"); + + impl_case!(888, -300 => "3.3588E-568"); + impl_case!(888, -19 => "3.3588E-287"); + impl_case!(888, 0 => "3.3588E-268"); + impl_case!(888, 270 => "335.88"); + + impl_case!(1022, 10 => "1.5423e-298"); + impl_case!(1022, 308 => "1.5423"); + + impl_case!(1038, 316 => "2353.4"); + + impl_case!(case_31028_n659: 31028, -659 => "3.0347E-10000"); + impl_case!(case_31028_0: 31028, 0 => "3.0347E-9341"); + impl_case!(case_31028_1: 31028, 1 => "3.0347E-9340"); + impl_case!(case_31028_9340: 31028, 9340 => ".30347"); + impl_case!(case_31028_10000: 31028, 10000 => "3.0347E+659"); + + // impl_case!(case_max: u64::MAX, 270 => "335.88"); +} #[cfg(test)] mod test { use super::*; use stdlib::num::NonZeroU64; + #[test] + fn test_inverse_35543972957198043e291() { + let v = vec![ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 2324389888, 849200558 + ]; + let x = BigInt::new(Sign::Minus, v); + let d = BigDecimal::from(x); + let expected = "-2.813416500187520746852694701086705659180043761702417561798711758892800449936819185796527214192677476E-308".parse().unwrap(); + assert_eq!(d.inverse(), expected); + + assert_eq!(d.neg().inverse(), expected.neg()); + } + macro_rules! impl_case { ($name:ident: $prec:literal, $round:ident => $expected:literal) => { #[test] From f298d80907a7d11e53cc569e3911dde3e97ce222 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Mon, 16 Oct 2023 01:03:49 -0400 Subject: [PATCH 73/77] Standardize division of BigDecimal by primitive integer --- src/impl_ops.rs | 2 +- src/impl_ops_div.rs | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/impl_ops.rs b/src/impl_ops.rs index 9cac1be..565d94b 100644 --- a/src/impl_ops.rs +++ b/src/impl_ops.rs @@ -304,7 +304,7 @@ macro_rules! impl_div_for_primitive { type Output = BigDecimal; fn div(self, denom: &BigDecimal) -> BigDecimal { - BigDecimal::from(self) / denom + self / denom.clone() } } }; diff --git a/src/impl_ops_div.rs b/src/impl_ops_div.rs index 1a756bd..9f36b9f 100644 --- a/src/impl_ops_div.rs +++ b/src/impl_ops_div.rs @@ -153,4 +153,16 @@ mod test { let x = BigDecimal::from_str("3.14").unwrap(); let _r = x / BigDecimal::zero(); } + + #[test] + fn test_division_by_large_number() { + let n = 1u8; + let d: BigDecimal = "79437738588056219546528239237352667078".parse().unwrap(); + + let quotient_n_ref_d = n / &d; + let quotient_n_d = n / d.clone(); + assert_eq!(quotient_n_ref_d, quotient_n_d); + + assert_eq!(quotient_n_ref_d, "1.258847517281104957975270408416632052090243053529147458917576143852500316808428812104171430669001064E-38".parse().unwrap()); + } } From f0ad6c329997a0a5ad299ccee19e0257364a9700 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Mon, 16 Oct 2023 01:38:16 -0400 Subject: [PATCH 74/77] Fix Rust 1.43 and no-std compatibility issues --- src/arithmetic/inverse.rs | 4 ++-- src/parsing.rs | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/arithmetic/inverse.rs b/src/arithmetic/inverse.rs index b209f07..398463e 100644 --- a/src/arithmetic/inverse.rs +++ b/src/arithmetic/inverse.rs @@ -49,7 +49,7 @@ fn make_inv_guess(bit_count: u64, scale: i64) -> BigDecimal { let magic_factor = stdlib::f64::consts::LN_2; let bit_count = bit_count as f64; - let initial_guess = magic_factor * 2f64.powf(-bit_count); + let initial_guess = magic_factor * exp2(-bit_count); if initial_guess.is_finite() && initial_guess != 0.0 { if let Ok(mut result) = BigDecimal::try_from(initial_guess) { result.scale -= scale; @@ -63,7 +63,7 @@ fn make_inv_guess(bit_count: u64, scale: i64) -> BigDecimal { let approx_scale_int = approx_scale.trunc(); let approx_scale_frac = approx_scale - approx_scale_int; - let recip = 10f64.powf(-approx_scale_frac); + let recip = libm::exp10(-approx_scale_frac); let mut res = BigDecimal::from_f32((magic_factor * recip) as f32).unwrap(); res.scale += approx_scale_int as i64; res.scale -= scale; diff --git a/src/parsing.rs b/src/parsing.rs index 83d5ed6..777c158 100644 --- a/src/parsing.rs +++ b/src/parsing.rs @@ -1,6 +1,7 @@ //! Routines for parsing values into BigDecimals use super::{BigDecimal, ParseBigDecimalError}; +use stdlib::num::FpCategory; use stdlib::cmp::{self, Ordering}; @@ -50,7 +51,7 @@ fn split_f32_into_parts(f: f32) -> (u32, i64, Sign) { /// Create bigdecimal from f32 /// pub(crate) fn parse_from_f32(n: f32) -> BigDecimal { - if n.is_subnormal() { + if n.classify() == FpCategory::Subnormal { return parse_from_f32_subnormal(n); } let bits = n.to_bits(); @@ -97,7 +98,7 @@ pub(crate) fn parse_from_f32(n: f32) -> BigDecimal { /// Create bigdecimal from subnormal f32 pub(crate) fn parse_from_f32_subnormal(n: f32) -> BigDecimal { - debug_assert!(n.is_subnormal()); + debug_assert_eq!(n.classify(), FpCategory::Subnormal); let bits = n.to_bits(); let sign_bit = bits >> 31; @@ -168,7 +169,7 @@ fn split_f64_into_parts(f: f64) -> (u64, i64, Sign) { /// Create bigdecimal from subnormal f64 pub(crate) fn parse_from_f64_subnormal(n: f64) -> BigDecimal { - debug_assert!(n.is_subnormal()); + debug_assert_eq!(n.classify(), FpCategory::Subnormal); let bits = n.to_bits(); let sign_bit = bits >> 63; @@ -204,7 +205,7 @@ pub(crate) fn parse_from_f64_subnormal(n: f64) -> BigDecimal { /// Non "normal" values is undefined behavior /// pub(crate) fn parse_from_f64(n: f64) -> BigDecimal { - if n.is_subnormal() { + if n.classify() == FpCategory::Subnormal { return parse_from_f64_subnormal(n); } From ff1def45582178fd3f87456701437819cf7fc224 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Mon, 16 Oct 2023 02:13:23 -0400 Subject: [PATCH 75/77] Fix panic in from_str_radix Reported-By: fritzrehde (github-issue #115) --- src/impl_num.rs | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/impl_num.rs b/src/impl_num.rs index 4953cd1..53dadab 100644 --- a/src/impl_num.rs +++ b/src/impl_num.rs @@ -85,7 +85,15 @@ impl Num for BigDecimal { } }; - let scale = decimal_offset - exponent_value; + let scale = match decimal_offset.checked_sub(exponent_value) { + Some(scale) => scale, + None => { + return Err(ParseBigDecimalError::Other( + format!("Exponent overflow when parsing '{}'", s) + )) + } + }; + let big_int = BigInt::from_str_radix(&digits, radix)?; Ok(BigDecimal::new(big_int, scale)) @@ -163,3 +171,20 @@ impl ToBigInt for BigDecimal { Some(self.with_scale(0).int_val) } } + + +#[cfg(test)] +mod test { + use super::*; + + mod from_str_radix { + use super::*; + + #[test] + fn out_of_bounds() { + let d = BigDecimal::from_str_radix("1e-9223372036854775808", 10); + assert_eq!(d.unwrap_err(), ParseBigDecimalError::Other("Exponent overflow when parsing '1e-9223372036854775808'".to_owned())); + + } + } +} From d6e992a8ef6cb61cdcb66c6c8366578004304800 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Mon, 16 Oct 2023 02:17:28 -0400 Subject: [PATCH 76/77] Fix clippy lints --- src/impl_num.rs | 2 +- src/impl_ops_add.rs | 10 +--------- src/impl_ops_sub.rs | 6 +++--- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/impl_num.rs b/src/impl_num.rs index 53dadab..8136973 100644 --- a/src/impl_num.rs +++ b/src/impl_num.rs @@ -183,7 +183,7 @@ mod test { #[test] fn out_of_bounds() { let d = BigDecimal::from_str_radix("1e-9223372036854775808", 10); - assert_eq!(d.unwrap_err(), ParseBigDecimalError::Other("Exponent overflow when parsing '1e-9223372036854775808'".to_owned())); + assert_eq!(d.unwrap_err(), ParseBigDecimalError::Other("Exponent overflow when parsing '1e-9223372036854775808'".to_string())); } } diff --git a/src/impl_ops_add.rs b/src/impl_ops_add.rs index f3cba79..8b80bfd 100644 --- a/src/impl_ops_add.rs +++ b/src/impl_ops_add.rs @@ -209,15 +209,7 @@ impl<'a, N: Into>> AddAssign for BigDecimal { impl AddAssign for BigDecimal { #[inline] fn add_assign(&mut self, rhs: BigInt) { - let sign = rhs.sign(); - let mag = rhs.magnitude(); - let rhs_ref = BigDecimalRef { - scale: 0, - sign: sign, - digits: mag.into(), - }; - - self.add_assign(rhs_ref); + self.add_assign(&rhs); } } diff --git a/src/impl_ops_sub.rs b/src/impl_ops_sub.rs index 619bd4f..5f5b305 100644 --- a/src/impl_ops_sub.rs +++ b/src/impl_ops_sub.rs @@ -89,7 +89,7 @@ impl<'a, T: Into>> Sub for BigDecimalRef<'_> { let rhs = rhs.into(); match self.scale.cmp(&rhs.scale) { - Ordering::Equal => self.clone() - rhs, + Ordering::Equal => self.to_owned() - rhs, Ordering::Less => self.to_owned_with_scale(rhs.scale) - rhs, Ordering::Greater => self - rhs.to_owned_with_scale(self.scale), } @@ -124,7 +124,7 @@ impl Sub for BigDecimalRef<'_> { } } -impl<'a> Sub for BigInt { +impl Sub for BigInt { type Output = BigDecimal; #[inline] @@ -133,7 +133,7 @@ impl<'a> Sub for BigInt { } } -impl<'a> Sub for &BigInt { +impl Sub for &BigInt { type Output = BigDecimal; #[inline] From a2ec320504fea2105f3d3edd362566b3213f67b6 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Mon, 16 Oct 2023 02:40:13 -0400 Subject: [PATCH 77/77] Version 0.4.2 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 85556b6..49bd37e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bigdecimal" -version = "0.4.2+dev" +version = "0.4.2" authors = ["Andrew Kubera"] description = "Arbitrary precision decimal numbers" documentation = "https://docs.rs/bigdecimal"