diff --git a/src/arithmetic/mod.rs b/src/arithmetic/mod.rs index 0e3e6de..c321f70 100644 --- a/src/arithmetic/mod.rs +++ b/src/arithmetic/mod.rs @@ -156,3 +156,26 @@ pub(crate) fn store_carry(n: u8, carry: &mut u8) -> u8 { n - 10 } } + + +/// Extend destination vector with values in D, adding carry while carry is not zero +/// +/// If carry overflows, it is NOT pushed into the destination vector. +/// +pub(crate) fn extend_adding_with_carry>( + dest: &mut Vec, + mut digits: D, + carry: &mut u8, +) { + while *carry != 0 { + match digits.next() { + Some(d) => { + dest.push(add_carry(d, carry)) + } + None => { + return; + } + } + } + dest.extend(digits); +} diff --git a/src/context.rs b/src/context.rs index 39ca6d1..2b0e58d 100644 --- a/src/context.rs +++ b/src/context.rs @@ -9,8 +9,6 @@ use arithmetic::store_carry; // 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 @@ -85,6 +83,17 @@ impl Context { self.rounding } + /// Round decimal to precision in this context, using rounding-mode + pub fn round_decimal(&self, n: BigDecimal) -> BigDecimal { + n.with_precision_round(self.precision(), self.rounding_mode()) + } + + /// Round decimal to precision in this context, using rounding-mode + pub fn round_decimal_ref<'a, D: Into>>(&self, n: D) -> BigDecimal { + let d = n.into().to_owned(); + d.with_precision_round(self.precision(), self.rounding_mode()) + } + /// 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) @@ -108,7 +117,7 @@ impl stdlib::default::Default for Context { fn default() -> Self { Self { precision: NonZeroU64::new(DEFAULT_PRECISION).unwrap(), - rounding: DEFAULT_ROUNDING_MODE, + rounding: RoundingMode::default(), } } } @@ -173,4 +182,48 @@ mod test_context { let sum = ctx.with_prec(27).unwrap().with_rounding_mode(RoundingMode::Up).add_refs(&a, neg_b); assert_eq!(sum, "209682.134972197165534775309".parse().unwrap()); } + + mod round_decimal_ref { + use super::*; + + #[test] + fn case_bigint_1234567_prec3() { + let ctx = Context::default().with_prec(3).unwrap(); + let i = BigInt::from(1234567); + let d = ctx.round_decimal_ref(&i); + assert_eq!(d.int_val, 123.into()); + assert_eq!(d.scale, -4); + } + + #[test] + fn case_bigint_1234500_prec4_halfup() { + let ctx = Context::default() + .with_prec(4).unwrap() + .with_rounding_mode(RoundingMode::HalfUp); + let i = BigInt::from(1234500); + let d = ctx.round_decimal_ref(&i); + assert_eq!(d.int_val, 1235.into()); + assert_eq!(d.scale, -3); + } + + #[test] + fn case_bigint_1234500_prec4_halfeven() { + let ctx = Context::default() + .with_prec(4).unwrap() + .with_rounding_mode(RoundingMode::HalfEven); + let i = BigInt::from(1234500); + let d = ctx.round_decimal_ref(&i); + assert_eq!(d.int_val, 1234.into()); + assert_eq!(d.scale, -3); + } + + #[test] + fn case_bigint_1234567_prec10() { + let ctx = Context::default().with_prec(10).unwrap(); + let i = BigInt::from(1234567); + let d = ctx.round_decimal_ref(&i); + assert_eq!(d.int_val, 1234567000.into()); + assert_eq!(d.scale, 3); + } + } } diff --git a/src/lib.rs b/src/lib.rs index abcdecd..310d12e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1235,6 +1235,14 @@ impl BigDecimalRef<'_> { } } + /// Create BigDecimal from this reference, rounding to precision and + /// with rounding-mode of the given context + /// + /// + pub fn round_with_context(&self, ctx: &Context) -> BigDecimal { + ctx.round_decimal_ref(*self) + } + /// Take square root of this number pub fn sqrt_with_context(&self, ctx: &Context) -> Option { use Sign::*; diff --git a/src/rounding.rs b/src/rounding.rs index fed29e2..172c5bc 100644 --- a/src/rounding.rs +++ b/src/rounding.rs @@ -2,9 +2,11 @@ #![allow(dead_code)] use crate::*; -use crate::arithmetic::{add_carry, store_carry}; +use crate::arithmetic::{add_carry, store_carry, extend_adding_with_carry}; use stdlib; +// const DEFAULT_ROUNDING_MODE: RoundingMode = ${RUST_BIGDECIMAL_DEFAULT_ROUNDING_MODE} or HalfUp; +include!(concat!(env!("OUT_DIR"), "/default_rounding_mode.rs")); /// Determines how to calculate the last digit of the number /// @@ -225,6 +227,16 @@ impl RoundingMode { } +/// Return compile-time constant default rounding mode +/// +/// Defined by RUST_BIGDECIMAL_DEFAULT_ROUNDING_MODE at compile time +/// +impl Default for RoundingMode { + fn default() -> Self { + DEFAULT_ROUNDING_MODE + } +} + /// All non-digit information required to round digits /// @@ -248,6 +260,11 @@ impl NonDigitRoundingData { pub fn round_pair_with_carry(&self, pair: (u8, u8), trailing_zeros: bool, carry: &mut u8) -> u8 { self.mode.round_pair_with_carry(self.sign, pair, trailing_zeros, carry) } + + /// Use sign and default rounding mode + pub fn default_with_sign(sign: Sign) -> Self { + NonDigitRoundingData { sign, mode: RoundingMode::default() } + } } @@ -273,22 +290,38 @@ pub(crate) struct InsigData { /// This is only useful if relevant for the rounding mode, it /// may be 'wrong' in these cases. pub trailing_zeros: bool, + + /// rounding-mode and sign + pub rounding_data: NonDigitRoundingData } impl InsigData { + /// Build from insig data and lazily calcuated trailing-zeros callable + pub fn from_digit_and_lazy_trailing_zeros( + rounder: NonDigitRoundingData, + insig_digit: u8, + calc_trailing_zeros: impl FnOnce() -> bool + ) -> Self { + Self { + digit: insig_digit, + trailing_zeros: rounder.mode.needs_trailing_zeros(insig_digit) && calc_trailing_zeros(), + rounding_data: rounder, + } + } + /// Build from slice of insignificant little-endian digits - pub fn from_digit_slice(mode: RoundingMode, digits: &[u8]) -> Self { + pub fn from_digit_slice(rounder: NonDigitRoundingData, digits: &[u8]) -> Self { match digits.split_last() { Some((&d0, trailing)) => { - Self { - digit: d0, - trailing_zeros: mode.needs_trailing_zeros(d0) && trailing.iter().all(Zero::is_zero), - } + Self::from_digit_and_lazy_trailing_zeros( + rounder, d0, || trailing.iter().all(Zero::is_zero) + ) } None => { Self { digit: 0, trailing_zeros: true, + rounding_data: rounder, } } } @@ -296,7 +329,7 @@ impl InsigData { /// from sum of overlapping digits, (a is longer than b) pub fn from_overlapping_digits_backward_sum( - mode: RoundingMode, + rounder: NonDigitRoundingData, mut a_digits: stdlib::iter::Rev>, mut b_digits: stdlib::iter::Rev>, carry: &mut u8, @@ -319,6 +352,7 @@ impl InsigData { return Self { digit: 0, trailing_zeros: true, + rounding_data: rounder, }; } }; @@ -341,15 +375,46 @@ impl InsigData { // if the last 'sum' value isn't zero, or if any remaining // digit is not zero, then it's not trailing zeros let trailing_zeros = sum == 0 - && mode.needs_trailing_zeros(insig_digit) + && rounder.mode.needs_trailing_zeros(insig_digit) && a_digits.all(Zero::is_zero) && b_digits.all(Zero::is_zero); Self { digit: insig_digit, trailing_zeros: trailing_zeros, + rounding_data: rounder, } } + + pub fn round_digit(&self, digit: u8) -> u8 { + self.rounding_data.round_pair((digit, self.digit), self.trailing_zeros) + } + + pub fn round_digit_with_carry(&self, digit: u8, carry: &mut u8) -> u8 { + self.rounding_data.round_pair_with_carry((digit, self.digit), self.trailing_zeros, carry) + } + + pub fn round_slice_into(&self, dest: &mut Vec, digits: &[u8]) { + let (&d0, rest) = digits.split_first().unwrap_or((&0, &[])); + let digits = rest.iter().copied(); + let mut carry = 0; + let r0 = self.round_digit_with_carry(d0, &mut carry); + dest.push(r0); + extend_adding_with_carry(dest, digits, &mut carry); + if !carry.is_zero() { + dest.push(carry); + } + } + + #[allow(dead_code)] + pub fn round_slice_into_with_carry(&self, dest: &mut Vec, digits: &[u8], carry: &mut u8) { + let (&d0, rest) = digits.split_first().unwrap_or((&0, &[])); + let digits = rest.iter().copied(); + let r0 = self.round_digit_with_carry(d0, carry); + dest.push(r0); + + extend_adding_with_carry(dest, digits, carry); + } }