Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 3 additions & 70 deletions src/uucore/src/lib/features/parser/num_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@

// spell-checker:ignore powf copysign prec ilog inity infinit infs bigdecimal extendedbigdecimal biguint underflowed muls

use std::num::NonZeroU64;

use bigdecimal::{
BigDecimal, Context,
BigDecimal,
num_bigint::{BigInt, BigUint, Sign},
};
use num_traits::Signed;
Expand Down Expand Up @@ -398,71 +396,6 @@ fn make_error(overflow: bool, negative: bool) -> ExtendedParserError<ExtendedBig
}
}

/// Compute bd**exp using exponentiation by squaring algorithm, while maintaining the
/// precision specified in ctx (the number of digits would otherwise explode).
///
/// Algorithm comes from <https://en.wikipedia.org/wiki/Exponentiation_by_squaring>
///
/// TODO: Still pending discussion in <https://github.com/akubera/bigdecimal-rs/issues/147>,
/// we do lose a little bit of precision, and the last digits may not be correct.
/// Note: This has been copied from the latest revision in <https://github.com/akubera/bigdecimal-rs/pull/148>,
/// so it's using minimum Rust version of `bigdecimal-rs`.
fn pow_with_context(bd: &BigDecimal, exp: i64, ctx: &Context) -> BigDecimal {
if exp == 0 {
return 1.into();
}

// When performing a multiplication between 2 numbers, we may lose up to 2 digits
// of precision.
// "Proof": https://github.com/akubera/bigdecimal-rs/issues/147#issuecomment-2793431202
const MARGIN_PER_MUL: u64 = 2;
// When doing many multiplication, we still introduce additional errors, add 1 more digit
// per 10 multiplications.
const MUL_PER_MARGIN_EXTRA: u64 = 10;

fn trim_precision(bd: BigDecimal, ctx: &Context, margin: u64) -> BigDecimal {
let prec = ctx.precision().get() + margin;
if bd.digits() > prec {
bd.with_precision_round(NonZeroU64::new(prec).unwrap(), ctx.rounding_mode())
} else {
bd
}
}

// Count the number of multiplications we're going to perform, one per "1" binary digit
// in exp, and the number of times we can divide exp by 2.
let mut n = exp.unsigned_abs();
// Note: 63 - n.leading_zeros() == n.ilog2, but that's only available in recent Rust versions.
let muls = (n.count_ones() + (63 - n.leading_zeros()) - 1) as u64;
// Note: div_ceil would be nice to use here, but only available in recent Rust versions.
// (see note above about minimum Rust version in use)
let margin_extra = (muls + MUL_PER_MARGIN_EXTRA / 2) / MUL_PER_MARGIN_EXTRA;
let mut margin = margin_extra + MARGIN_PER_MUL * muls;

let mut bd_y: BigDecimal = 1.into();
let mut bd_x = if exp >= 0 {
bd.clone()
} else {
bd.inverse_with_context(&ctx.with_precision(
NonZeroU64::new(ctx.precision().get() + margin + MARGIN_PER_MUL).unwrap(),
))
};

while n > 1 {
if n % 2 == 1 {
bd_y = trim_precision(&bd_x * bd_y, ctx, margin);
margin -= MARGIN_PER_MUL;
n -= 1;
}
bd_x = trim_precision(bd_x.square(), ctx, margin);
margin -= MARGIN_PER_MUL;
n /= 2;
}
debug_assert_eq!(margin, margin_extra);

trim_precision(bd_x * bd_y, ctx, 0)
}

/// Construct an [`ExtendedBigDecimal`] based on parsed data
fn construct_extended_big_decimal(
digits: BigUint,
Expand Down Expand Up @@ -510,7 +443,7 @@ fn construct_extended_big_decimal(
let bd = BigDecimal::from_bigint(signed_digits, 0)
/ BigDecimal::from_bigint(BigInt::from(16).pow(scale as u32), 0);

// pow_with_context "only" supports i64 values. Just overflow/underflow if the value provided
// powi "only" supports i64 values. Just overflow/underflow if the value provided
// is > 2**64 or < 2**-64.
let Some(exponent) = exponent.to_i64() else {
return Err(make_error(exponent.is_positive(), negative));
Expand All @@ -520,7 +453,7 @@ fn construct_extended_big_decimal(
let base: BigDecimal = 2.into();
// Note: We cannot overflow/underflow BigDecimal here, as we will not be able to reach the
// maximum/minimum scale (i64 range).
let pow2 = pow_with_context(&base, exponent, &Context::default());
let pow2 = base.powi(exponent);

bd * pow2
} else {
Expand Down
Loading