const MAX_DIGITS: usize = 5120;
const MAX_SHIFT: i32 = 60;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Fraction {
Zero,
BelowHalf,
Half,
AboveHalf,
}
#[derive(Debug, Clone)]
pub(crate) struct Decimal {
digits: Vec<u8>,
point: i32,
truncated: bool,
scratch: Vec<u8>,
}
impl Decimal {
pub(crate) fn new(digits: Vec<u8>, point: i32) -> Decimal {
let mut decimal = Decimal { digits, point, truncated: false, scratch: Vec::new() };
decimal.trim();
decimal
}
pub(crate) fn is_zero(&self) -> bool {
self.digits.is_empty()
}
pub(crate) fn point(&self) -> i32 {
self.point
}
pub(crate) fn first_digit(&self) -> u8 {
self.digits.first().copied().unwrap_or(0)
}
pub(crate) fn shift(&mut self, mut k: i32) {
while k > 0 && !self.is_zero() {
let pass = k.min(MAX_SHIFT);
self.shift_left(pass as u32);
k -= pass;
}
while k < 0 && !self.is_zero() {
let pass = (-k).min(MAX_SHIFT);
self.shift_right(pass as u32);
k += pass;
}
}
pub(crate) fn round_to_u128(&self) -> (u128, Fraction) {
let taken = self.point.clamp(0, self.digits.len() as i32) as usize;
let mut integer: u128 = 0;
for &digit in &self.digits[..taken] {
integer = integer.saturating_mul(10).saturating_add(u128::from(digit));
}
for _ in taken as i32..self.point {
integer = integer.saturating_mul(10);
}
let rest = &self.digits[taken.min(self.digits.len())..];
if self.point < 0 || rest.is_empty() {
let anything = !rest.is_empty() || self.truncated;
return (integer, if anything { Fraction::BelowHalf } else { Fraction::Zero });
}
let leading = rest[0];
let more = rest[1..].iter().any(|&digit| digit != 0) || self.truncated;
let fraction = match leading {
6..=9 => Fraction::AboveHalf,
5 if more => Fraction::AboveHalf,
5 => Fraction::Half,
0 if !more => Fraction::Zero,
_ => Fraction::BelowHalf,
};
(integer, fraction)
}
fn shift_left(&mut self, k: u32) {
let mut carry: u64 = 0;
self.scratch.clear();
for &digit in self.digits.iter().rev() {
let value = (u64::from(digit) << k) + carry;
self.scratch.push((value % 10) as u8);
carry = value / 10;
}
while carry > 0 {
self.scratch.push((carry % 10) as u8);
carry /= 10;
}
self.scratch.reverse();
self.point += (self.scratch.len() - self.digits.len()) as i32;
std::mem::swap(&mut self.digits, &mut self.scratch);
self.trim();
}
fn shift_right(&mut self, k: u32) {
let divisor: u64 = 1 << k;
let mask = divisor - 1;
self.scratch.clear();
let mut read = 0;
let mut remainder: u64 = 0;
while remainder >> k == 0 {
if read >= self.digits.len() {
if remainder == 0 {
self.digits.clear();
self.point = 0;
return;
}
while remainder >> k == 0 {
remainder *= 10;
read += 1;
}
break;
}
remainder = remainder * 10 + u64::from(self.digits[read]);
read += 1;
}
self.point -= read as i32 - 1;
while read < self.digits.len() {
let digit = u64::from(self.digits[read]);
self.scratch.push((remainder >> k) as u8);
remainder = (remainder & mask) * 10 + digit;
read += 1;
}
while remainder > 0 {
let digit = (remainder >> k) as u8;
if self.scratch.len() < MAX_DIGITS {
self.scratch.push(digit);
} else if digit > 0 {
self.truncated = true;
}
remainder = (remainder & mask) * 10;
}
std::mem::swap(&mut self.digits, &mut self.scratch);
self.trim();
}
fn trim(&mut self) {
let leading = self.digits.iter().take_while(|&&digit| digit == 0).count();
if leading > 0 {
self.digits.drain(..leading);
self.point -= leading as i32;
}
while self.digits.last() == Some(&0) {
self.digits.pop();
}
if self.digits.len() > MAX_DIGITS {
if self.digits[MAX_DIGITS..].iter().any(|&digit| digit != 0) {
self.truncated = true;
}
self.digits.truncate(MAX_DIGITS);
while self.digits.last() == Some(&0) {
self.digits.pop();
}
}
if self.digits.is_empty() && !self.truncated {
self.point = 0;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn decimal(digits: &str, point: i32) -> Decimal {
Decimal::new(digits.bytes().map(|byte| byte - b'0').collect(), point)
}
fn spell(value: &Decimal) -> String {
let digits: String =
value.digits.iter().map(|&digit| char::from(b'0' + digit)).collect::<String>();
format!("0.{digits}e{}", value.point)
}
#[test]
fn zeros_that_do_not_mean_anything_are_dropped() {
assert_eq!(spell(&decimal("00123400", 5)), "0.1234e3");
assert!(decimal("0000", 7).is_zero());
assert_eq!(spell(&decimal("1", 1)), "0.1e1");
}
#[test]
fn doubling_and_halving_give_back_the_number() {
let mut value = decimal("123456789", 5);
let before = spell(&value);
value.shift(60);
value.shift(-60);
assert_eq!(spell(&value), before);
}
#[test]
fn halving_is_exact_however_long_it_takes() {
let mut value = decimal("1", 1);
value.shift(-1);
assert_eq!(spell(&value), "0.5e0");
value.shift(-1);
assert_eq!(spell(&value), "0.25e0");
value.shift(-2);
assert_eq!(spell(&value), "0.625e-1");
}
#[test]
fn doubling_carries_across_the_whole_number() {
let mut value = decimal("5", 1);
value.shift(1);
assert_eq!(spell(&value), "0.1e2");
let mut big = decimal("999999999999999999999999", 24);
big.shift(1);
assert_eq!(spell(&big), "0.1999999999999999999999998e25");
}
#[test]
fn a_shift_larger_than_one_pass_is_still_one_number() {
let mut value = decimal("1", 1);
value.shift(200);
assert_eq!(
spell(&value),
"0.1606938044258990275541962092341162602522202993782792835301376e61"
);
}
#[test]
fn the_integer_part_comes_back_with_where_the_rest_sits() {
assert_eq!(decimal("125", 1).round_to_u128(), (1, Fraction::BelowHalf));
assert_eq!(decimal("15", 1).round_to_u128(), (1, Fraction::Half));
assert_eq!(decimal("1500001", 1).round_to_u128(), (1, Fraction::AboveHalf));
assert_eq!(decimal("175", 1).round_to_u128(), (1, Fraction::AboveHalf));
assert_eq!(decimal("1", 1).round_to_u128(), (1, Fraction::Zero));
assert_eq!(decimal("1", 3).round_to_u128(), (100, Fraction::Zero));
assert_eq!(decimal("9", 0).round_to_u128(), (0, Fraction::AboveHalf));
assert_eq!(decimal("9", -3).round_to_u128(), (0, Fraction::BelowHalf));
}
#[test]
fn a_number_longer_than_the_buffer_is_remembered_as_truncated() {
let long = "1".repeat(MAX_DIGITS + 10);
let value = decimal(&long, 1);
assert!(value.truncated);
assert_eq!(value.digits.len(), MAX_DIGITS);
let mut tie = "5".to_string();
tie.push_str(&"0".repeat(MAX_DIGITS));
tie.push('1');
assert_eq!(decimal(&tie, 0).round_to_u128(), (0, Fraction::AboveHalf));
}
#[test]
fn trailing_zeros_past_the_buffer_are_not_a_truncation() {
let mut padded = "5".to_string();
padded.push_str(&"0".repeat(MAX_DIGITS * 2));
let value = decimal(&padded, 0);
assert!(!value.truncated);
assert_eq!(value.round_to_u128(), (0, Fraction::Half));
}
}