Skip to content

Commit

Permalink
Merge updated rounding methods into trunk
Browse files Browse the repository at this point in the history
  • Loading branch information
akubera committed Oct 27, 2024
2 parents 1cd14c7 + 145efc5 commit 3c74e8d
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 11 deletions.
23 changes: 23 additions & 0 deletions src/arithmetic/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<D: Iterator<Item=u8>>(
dest: &mut Vec<u8>,
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);
}
59 changes: 56 additions & 3 deletions src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<BigDecimalRef<'a>>>(&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)
Expand All @@ -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(),
}
}
}
Expand Down Expand Up @@ -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);
}
}
}
8 changes: 8 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<BigDecimal> {
use Sign::*;
Expand Down
81 changes: 73 additions & 8 deletions src/rounding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
///
Expand Down Expand Up @@ -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
///
Expand All @@ -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() }
}
}


Expand All @@ -273,30 +290,46 @@ 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,
}
}
}
}

/// 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<stdlib::slice::Iter<u8>>,
mut b_digits: stdlib::iter::Rev<stdlib::slice::Iter<u8>>,
carry: &mut u8,
Expand All @@ -319,6 +352,7 @@ impl InsigData {
return Self {
digit: 0,
trailing_zeros: true,
rounding_data: rounder,
};
}
};
Expand All @@ -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<u8>, 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<u8>, 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);
}
}


Expand Down

0 comments on commit 3c74e8d

Please sign in to comment.