Shared Rust kernels
decimal
All modules · quoted at commit 9e1884369a67 of DnaCalc/DnaCalc. Comments are the source's own.
low32 · 4 lines · calc-func/src/decimal.rs on GitHub/// The low 32 bits of `value`. The mask keeps the narrowing exact.
fn low32(value: u64) -> u32 {
(value & 0xFFFF_FFFF) as u32
}
Used by 62 functions.
Natural::zero · 3 lines · calc-func/src/decimal.rs on GitHubpub(crate) fn zero() -> Self {
Natural { limbs: Vec::new() }
}
Used by 25 functions.
Natural::from_u64 · 7 lines · calc-func/src/decimal.rs on GitHubpub(crate) fn from_u64(value: u64) -> Self {
let mut natural = Natural {
limbs: vec![low32(value), low32(value >> 32)],
};
natural.normalize();
natural
}
Used by 62 functions.
Natural::pow10 · 6 lines · calc-func/src/decimal.rs on GitHub/// 10^exponent.
pub(crate) fn pow10(exponent: u32) -> Self {
let mut power = Natural::from_u64(1);
power.times_pow10(exponent);
power
}
Used by 61 functions.
Natural::normalize · 5 lines · calc-func/src/decimal.rs on GitHubfn normalize(&mut self) {
while self.limbs.last() == Some(&0) {
self.limbs.pop();
}
}
Used by 62 functions.
Natural::is_zero · 3 lines · calc-func/src/decimal.rs on GitHubpub(crate) fn is_zero(&self) -> bool {
self.limbs.is_empty()
}
Used by 62 functions.
Natural::mul_add_small · 13 lines · calc-func/src/decimal.rs on GitHub/// self * factor + addend.
fn mul_add_small(&mut self, factor: u32, addend: u32) {
let mut carry = u64::from(addend);
for limb in &mut self.limbs {
let product = u64::from(*limb) * u64::from(factor) + carry;
*limb = low32(product);
carry = product >> 32;
}
if carry != 0 {
self.limbs.push(low32(carry));
}
self.normalize();
}
Used by 62 functions.
Natural::add_one · 3 lines · calc-func/src/decimal.rs on GitHubpub(crate) fn add_one(&mut self) {
self.mul_add_small(1, 1);
}
Used by 5 functions.
Natural::times_pow10 · 7 lines · calc-func/src/decimal.rs on GitHubpub(crate) fn times_pow10(&mut self, mut exponent: u32) {
while exponent >= 9 {
self.mul_add_small(1_000_000_000, 0);
exponent -= 9;
}
self.mul_add_small(10u32.pow(exponent), 0);
}
Used by 61 functions.
Natural::times_pow5 · 7 lines · calc-func/src/decimal.rs on GitHubfn times_pow5(&mut self, mut exponent: u32) {
while exponent >= 13 {
self.mul_add_small(1_220_703_125, 0);
exponent -= 13;
}
self.mul_add_small(5u32.pow(exponent), 0);
}
Used by 62 functions.
Natural::times_pow2 · 19 lines · calc-func/src/decimal.rs on GitHubfn times_pow2(&mut self, exponent: u32) {
if self.is_zero() {
return;
}
let whole = usize::try_from(exponent / 32).expect("a limb count");
let bits = exponent % 32;
if bits != 0 {
let mut carry = 0u32;
for limb in &mut self.limbs {
let shifted = (u64::from(*limb) << bits) | u64::from(carry);
*limb = low32(shifted);
carry = low32(shifted >> 32);
}
if carry != 0 {
self.limbs.push(carry);
}
}
self.limbs.splice(0..0, std::iter::repeat_n(0, whole));
}
Used by 62 functions.
Natural::add · 16 lines · calc-func/src/decimal.rs on GitHub/// self + other.
pub(crate) fn add(&mut self, other: &Natural) {
if self.limbs.len() < other.limbs.len() {
self.limbs.resize(other.limbs.len(), 0);
}
let mut carry = 0u64;
for (at, limb) in self.limbs.iter_mut().enumerate() {
let sum =
u64::from(*limb) + u64::from(other.limbs.get(at).copied().unwrap_or(0)) + carry;
*limb = low32(sum);
carry = sum >> 32;
}
if carry != 0 {
self.limbs.push(low32(carry));
}
}
Used by 5 functions.
Natural::subtract · 17 lines · calc-func/src/decimal.rs on GitHub/// self - other, where other is at most self.
pub(crate) fn subtract(&mut self, other: &Natural) {
debug_assert!(*self >= *other, "a natural subtracted from a smaller one");
let mut borrow = 0u64;
for (at, limb) in self.limbs.iter_mut().enumerate() {
let taken = u64::from(other.limbs.get(at).copied().unwrap_or(0)) + borrow;
let current = u64::from(*limb);
if current >= taken {
*limb = low32(current - taken);
borrow = 0;
} else {
*limb = low32((1u64 << 32) + current - taken);
borrow = 1;
}
}
self.normalize();
}
Used by 5 functions.
Natural::bit_len · 9 lines · calc-func/src/decimal.rs on GitHubfn bit_len(&self) -> usize {
match self.limbs.last() {
None => 0,
Some(top) => {
(self.limbs.len() - 1) * 32
+ usize::try_from(32 - top.leading_zeros()).expect("a bit count")
}
}
}
Used by 5 functions.
Natural::bit · 3 lines · calc-func/src/decimal.rs on GitHubfn bit(&self, at: usize) -> bool {
(self.limbs[at / 32] >> (at % 32)) & 1 == 1
}
Used by 5 functions.
Natural::pow2 · 6 lines · calc-func/src/decimal.rs on GitHub/// 2^exponent.
fn pow2(exponent: u32) -> Self {
let mut power = Natural::from_u64(1);
power.times_pow2(exponent);
power
}
Used by 5 functions.
Natural::from_u128 · 9 lines · calc-func/src/decimal.rs on GitHubfn from_u128(value: u128) -> Self {
let mut natural = Natural {
limbs: (0..4)
.map(|at| low32((value >> (32 * at)) as u64))
.collect(),
};
natural.normalize();
natural
}
Used by 5 functions.
Natural::to_u128 · 9 lines · calc-func/src/decimal.rs on GitHub/// The value when it is below 2^128.
fn to_u128(&self) -> Option<u128> {
(self.limbs.len() <= 4).then(|| {
self.limbs
.iter()
.rev()
.fold(0u128, |value, &limb| (value << 32) | u128::from(limb))
})
}
Used by 5 functions.
Natural::shift_in · 4 lines · calc-func/src/decimal.rs on GitHub/// self * 2 + incoming.
fn shift_in(&mut self, incoming: bool) {
self.mul_add_small(2, u32::from(incoming));
}
Used by 5 functions.
Natural::div_rem · 25 lines · calc-func/src/decimal.rs on GitHub/// self / divisor and self % divisor, for a divisor that is not zero.
pub(crate) fn div_rem(&self, divisor: &Natural) -> (Natural, Natural) {
assert!(!divisor.is_zero(), "a natural divided by zero");
if *self < *divisor {
return (Natural::zero(), self.clone());
}
if let [single] = divisor.limbs[..] {
let mut quotient = self.clone();
let remainder = quotient.div_small(single);
return (quotient, Natural::from_u64(u64::from(remainder)));
}
let mut quotient = Natural {
limbs: vec![0; self.limbs.len()],
};
let mut remainder = Natural::zero();
for at in (0..self.bit_len()).rev() {
remainder.shift_in(self.bit(at));
if remainder >= *divisor {
remainder.subtract(divisor);
quotient.limbs[at / 32] |= 1 << (at % 32);
}
}
quotient.normalize();
(quotient, remainder)
}
Used by 5 functions.
Natural::div_small · 11 lines · calc-func/src/decimal.rs on GitHub/// Divides self by a non-zero `divisor` in place and returns the remainder.
fn div_small(&mut self, divisor: u32) -> u32 {
let mut remainder = 0u64;
for limb in self.limbs.iter_mut().rev() {
let current = (remainder << 32) | u64::from(*limb);
*limb = low32(current / u64::from(divisor));
remainder = current % u64::from(divisor);
}
self.normalize();
low32(remainder)
}
Used by 5 functions.
Natural::digits · 19 lines · calc-func/src/decimal.rs on GitHub/// The decimal digits, without leading zeros ("0" for zero).
pub(crate) fn digits(&self) -> String {
let mut rest = self.clone();
let mut chunks = Vec::new();
while !rest.is_zero() {
chunks.push(rest.div_small(1_000_000_000));
}
let mut text = String::new();
match chunks.split_last() {
None => text.push('0'),
Some((top, lower)) => {
let _ = write!(text, "{top}");
for chunk in lower.iter().rev() {
let _ = write!(text, "{chunk:09}");
}
}
}
text
}
Used by 5 functions.
Natural::cmp · 6 lines · calc-func/src/decimal.rs on GitHubfn cmp(&self, other: &Self) -> Ordering {
self.limbs
.len()
.cmp(&other.limbs.len())
.then_with(|| self.limbs.iter().rev().cmp(other.limbs.iter().rev()))
}
Used by 5 functions.
ExactDecimal::read · 35 lines · calc-func/src/decimal.rs on GitHub/// How the rounding and integer functions read a finite number. When its shortest decimal
/// that converts back to the same binary64 has at most 15 significant digits, the number is
/// that decimal: every number typed into a cell, which Excel reads to 15 significant digits,
/// reads back so (2.675, whose binary64 lies below 2.675, reads as 2.675). Any other number is
/// the exact value of its binary64 (0.1+0.2 reads as 0.3000000000000000444...). Zero of
/// either sign reads as +0.
pub(crate) fn read(x: f64) -> ExactDecimal {
let negative = x < 0.0;
let magnitude = x.abs();
if magnitude == 0.0 {
return ExactDecimal {
negative: false,
coefficient: Natural::zero(),
exponent: 0,
};
}
let shortest = format!("{magnitude:e}");
let (mantissa, power) = shortest
.split_once('e')
.expect("an exponent in the shortest form");
let power: i32 = power.parse().expect("a decimal exponent");
let (whole, fraction) = mantissa.split_once('.').unwrap_or((mantissa, ""));
if whole.len() + fraction.len() <= SHOWN_DIGITS {
let coefficient: u64 = format!("{whole}{fraction}")
.parse()
.expect("at most 15 decimal digits");
let scale = i32::try_from(fraction.len()).expect("a short fraction");
return ExactDecimal {
negative,
coefficient: Natural::from_u64(coefficient),
exponent: power - scale,
};
}
ExactDecimal::binary(x)
}
Used by 25 functions.
ExactDecimal::fifteen · 17 lines · calc-func/src/decimal.rs on GitHub/// How TRUNC, ROUND, ROUNDUP and ROUNDDOWN read a finite number (dc-bd5): [`ExactDecimal::read`]
/// rounded to 15 significant digits, a half away from zero, with whether that rounding changed
/// it. Excel 16.0.20430 (cfunc_m3_2's capture): ROUND(2.6749999999999994,2) is 2.68,
/// TRUNC(2.9999999999999996) is 3, TRUNC(-4503599627370495.5) is -4503599627370500 and
/// ROUND(9007199254740992,0) is 9007199254740990.
pub(crate) fn fifteen(x: f64) -> (ExactDecimal, bool) {
let read = ExactDecimal::read(x);
let length = i32::try_from(read.coefficient.digits().len()).expect("a digit count");
let excess = length - i32::try_from(SHOWN_DIGITS).expect("15");
if excess <= 0 {
return (read, false);
}
match read.round_at(read.exponent + excess, Direction::Nearest) {
Some(rounded) => (rounded, true),
None => (read, false),
}
}
Used by 5 functions.
ExactDecimal::unit · 8 lines · calc-func/src/decimal.rs on GitHub/// 1 * 10^exponent.
pub(crate) fn unit(exponent: i32) -> ExactDecimal {
ExactDecimal {
negative: false,
coefficient: Natural::from_u64(1),
exponent,
}
}
Used by 3 functions.
ExactDecimal::magnitude · 5 lines · calc-func/src/decimal.rs on GitHub/// The decimal exponent of a non-zero decimal: the e with 10^e <= |self| < 10^(e+1).
pub(crate) fn magnitude(&self) -> i32 {
let length = i32::try_from(self.coefficient.digits().len()).expect("a digit count");
length - 1 + self.exponent
}
Used by 5 functions.
ExactDecimal::fifteen_x87 · 59 lines · calc-func/src/decimal.rs on GitHub/// How ROUND (`ties_up` false) and ROUNDUP, ROUNDDOWN and TRUNC (`ties_up` true) read a
/// finite non-zero number to fifteen significant digits, where the scaling is exact on the
/// x87 unit: |x| scaled by 10^n, n = 14 - e for the decimal exponent e of |x|, is rounded
/// once to 64 significant bits (the x87 unit at 64-bit precision; 10^|n| is exact there for
/// |n| <= 27), and that value is rounded to an integer, a half up for the directed functions
/// and down for ROUND. So ROUND(1594628480062905,14) is 1594628480062900 and
/// ROUNDDOWN(283025097725723.5,0) is 283025097725724, as Excel gives them, and
/// ROUNDDOWN(53311.02038735315,10), whose 10^10 multiple lies 0.2 * 2^-64 below a half,
/// reads the half. `None` when |n| > 27: how Excel scales by a larger power is not known.
pub(crate) fn fifteen_x87(x: f64, ties_up: bool) -> Option<ExactDecimal> {
let shift = 14 - ExactDecimal::binary(x).magnitude();
if shift.abs() > 27 {
return None;
}
let bits = x.abs().to_bits();
let biased = i32::try_from((bits >> 52) & 0x7FF).expect("an 11-bit exponent");
let fraction_bits = bits & ((1u64 << 52) - 1);
let (mantissa, exponent) = if biased == 0 {
(fraction_bits, -1074)
} else {
(fraction_bits | (1u64 << 52), biased - 1075)
};
let mut numerator = Natural::from_u64(mantissa);
let mut denominator = Natural::from_u64(1);
if shift >= 0 {
numerator.times_pow10(shift.unsigned_abs());
} else {
denominator = Natural::pow10(shift.unsigned_abs());
}
// The scaled value rounded to 64 bits: kept * 2^power.
let (kept, power) = round_to_64_bits(&numerator, exponent, &denominator);
let whole = if power >= 0 {
let mut whole = Natural::from_u128(kept);
whole.times_pow2(power.unsigned_abs());
whole
} else {
let drop = power.unsigned_abs();
let (whole, rest) = if drop >= 128 {
(0, kept)
} else {
(kept >> drop, kept - ((kept >> drop) << drop))
};
let half = if drop > 128 {
u128::MAX
} else {
1u128 << (drop - 1)
};
let up = rest > half || (rest == half && ties_up);
Natural::from_u128(whole + u128::from(up))
};
Some(
ExactDecimal {
negative: x < 0.0,
coefficient: whole,
exponent: -shift,
}
.normalized(),
)
}
Used by 5 functions.
ExactDecimal::normalized · 12 lines · calc-func/src/decimal.rs on GitHub/// The same value with no trailing zero in its coefficient.
pub(crate) fn normalized(mut self) -> ExactDecimal {
while !self.coefficient.is_zero() {
let mut shorter = self.coefficient.clone();
if shorter.div_small(10) != 0 {
break;
}
self.coefficient = shorter;
self.exponent += 1;
}
self
}
Used by 5 functions.
ExactDecimal::binary · 30 lines · calc-func/src/decimal.rs on GitHub/// The exact value of a finite binary64, whatever decimal it shows. Zero of either sign is +0.
pub(crate) fn binary(x: f64) -> ExactDecimal {
let negative = x < 0.0;
// The binary64 is mantissa * 2^exponent exactly, with an integer mantissa below 2^53.
let bits = x.abs().to_bits();
let biased = i32::try_from((bits >> 52) & 0x7FF).expect("an 11-bit exponent");
let fraction_bits = bits & ((1u64 << 52) - 1);
let (mantissa, exponent) = if biased == 0 {
(fraction_bits, -1074)
} else {
(fraction_bits | (1u64 << 52), biased - 1075)
};
let mut coefficient = Natural::from_u64(mantissa);
if exponent >= 0 {
coefficient.times_pow2(exponent.unsigned_abs());
ExactDecimal {
negative,
coefficient,
exponent: 0,
}
} else {
// m * 2^-k = m * 5^k * 10^-k.
coefficient.times_pow5(exponent.unsigned_abs());
ExactDecimal {
negative,
coefficient,
exponent,
}
}
}
Used by 62 functions.
ExactDecimal::round_at · 35 lines · calc-func/src/decimal.rs on GitHub/// This decimal rounded in `direction` to a multiple of 10^position, or `None` when it has
/// no digit below that position (it is already such a multiple, unchanged).
pub(crate) fn round_at(&self, position: i32, direction: Direction) -> Option<ExactDecimal> {
if self.coefficient.is_zero() || position <= self.exponent {
return None;
}
let dropped = position.abs_diff(self.exponent);
let length = self.coefficient.digits().len();
let (mut kept, bump) = if usize::try_from(dropped).expect("a digit count") > length {
// Every digit lies below the position and the leading one more than one place
// below it: the dropped part is not zero and is less than half a unit.
(Natural::zero(), direction == Direction::AwayFromZero)
} else {
let unit = Natural::pow10(dropped);
let (kept, dropped_part) = self.coefficient.div_rem(&unit);
let bump = match direction {
Direction::Nearest => {
let mut twice = dropped_part.clone();
twice.add(&dropped_part);
twice >= unit
}
Direction::AwayFromZero => !dropped_part.is_zero(),
Direction::TowardZero => false,
};
(kept, bump)
};
if bump {
kept.add_one();
}
Some(ExactDecimal {
negative: self.negative,
coefficient: kept,
exponent: position,
})
}
Used by 5 functions.
round_to_64_bits · 27 lines · calc-func/src/decimal.rs on GitHub/// numerator * 2^exponent / denominator rounded to 64 significant bits, to the nearest and a
/// tie to even, as `(kept, power)` with the value kept * 2^power and kept below 2^64.
fn round_to_64_bits(numerator: &Natural, exponent: i32, denominator: &Natural) -> (u128, i32) {
// Scale so the integer quotient has at least 66 bits.
let scale = (66 + denominator.bit_len()).saturating_sub(numerator.bit_len());
let mut scaled = numerator.clone();
scaled.times_pow2(u32::try_from(scale).expect("a bit count"));
let (quotient, remainder) = scaled.div_rem(denominator);
let drop = u32::try_from(quotient.bit_len() - 64).expect("a bit count");
let (high, low) = quotient.div_rem(&Natural::pow2(drop));
let mut kept = high.to_u128().expect("64 bits");
let up = match low.cmp(&Natural::pow2(drop - 1)) {
Ordering::Greater => true,
Ordering::Less => false,
Ordering::Equal => !remainder.is_zero() || kept & 1 == 1,
};
let mut power = exponent - i32::try_from(scale).expect("a bit count")
+ i32::try_from(drop).expect("a bit count");
if up {
kept += 1;
if kept == 1u128 << 64 {
kept >>= 1;
power += 1;
}
}
(kept, power)
}
Used by 5 functions.
truncate · 15 lines · calc-func/src/decimal.rs on GitHub/// The integer part of a binary64, toward zero, found from its bits: exact on every target.
pub(crate) fn truncate(x: f64) -> f64 {
let bits = x.to_bits();
let unbiased = i32::try_from((bits >> 52) & 0x7FF).expect("an 11-bit exponent") - 1023;
if unbiased >= 52 {
// Every binary64 of this size is an integer (or not finite).
x
} else if unbiased < 0 {
// Below 1 in magnitude: the integer part is a zero of the number's sign.
f64::from_bits(bits & (1u64 << 63))
} else {
let fraction_mask = (1u64 << (52 - unbiased.unsigned_abs())) - 1;
f64::from_bits(bits & !fraction_mask)
}
}
Used by 13 functions.
floor · 6 lines · calc-func/src/decimal.rs on GitHub/// The largest integer not above `x`: exact, since a binary64 below 2^52 in magnitude that is
/// not an integer has an integer part one step from it.
pub(crate) fn floor(x: f64) -> f64 {
let whole = truncate(x);
if whole > x { whole - 1.0 } else { whole }
}
Used by 5 functions.
ceil · 5 lines · calc-func/src/decimal.rs on GitHub/// The smallest integer not below `x`.
pub(crate) fn ceil(x: f64) -> f64 {
let whole = truncate(x);
if whole < x { whole + 1.0 } else { whole }
}
Used by 5 functions.