#![cfg_attr(not(feature = "arbitrary_precision"), allow(dead_code))]
use std::cmp::Ordering;
use std::num::NonZeroU64;
const fn nonzero(x: u64) -> NonZeroU64 {
match NonZeroU64::new(x) {
Some(d) => d,
None => panic!("a divisor is non-zero"),
}
}
pub(crate) const TEN: NonZeroU64 = nonzero(10);
pub(crate) fn pow5(n: u32) -> NonZeroU64 {
const TABLE: [NonZeroU64; POW5_CHUNK_EXP as usize + 1] = {
let mut table = [NonZeroU64::MIN; POW5_CHUNK_EXP as usize + 1];
let mut i = 0;
while i < table.len() {
table[i] = nonzero(5u64.pow(i as u32));
i += 1;
}
table
};
TABLE[n as usize]
}
const POW10_CHUNK: NonZeroU64 = nonzero(10_000_000_000_000_000_000);
const POW10_CHUNK_EXP: u64 = 19;
const POW5_CHUNK: u64 = 7_450_580_596_923_828_125;
const POW5_CHUNK_EXP: u64 = 27;
pub(crate) fn trim(limbs: &mut Vec<u64>) {
while limbs.last() == Some(&0) {
limbs.pop();
}
}
pub(crate) fn is_zero(limbs: &[u64]) -> bool {
limbs.is_empty()
}
pub(crate) fn mul_small(limbs: &mut Vec<u64>, m: u64) {
if m == 0 {
limbs.clear();
return;
}
let mut carry: u64 = 0;
for limb in limbs.iter_mut() {
let wide = u128::from(*limb) * u128::from(m) + u128::from(carry);
*limb = wide as u64;
carry = (wide >> 64) as u64;
}
if carry != 0 {
limbs.push(carry);
}
}
pub(crate) fn add_small(limbs: &mut Vec<u64>, a: u64) {
let mut carry = a;
for limb in limbs.iter_mut() {
if carry == 0 {
return;
}
let (sum, overflowed) = limb.overflowing_add(carry);
*limb = sum;
carry = u64::from(overflowed);
}
if carry != 0 {
limbs.push(carry);
}
}
pub(crate) fn div_small(limbs: &mut Vec<u64>, d: NonZeroU64) -> u64 {
let d = u128::from(d.get());
let mut rem: u64 = 0;
for limb in limbs.iter_mut().rev() {
let wide = (u128::from(rem) << 64) | u128::from(*limb);
*limb = (wide / d) as u64;
rem = (wide % d) as u64;
}
trim(limbs);
rem
}
pub(crate) fn rem_small(limbs: &[u64], d: NonZeroU64) -> u64 {
let d = u128::from(d.get());
let mut rem: u64 = 0;
for &limb in limbs.iter().rev() {
let wide = (u128::from(rem) << 64) | u128::from(limb);
rem = (wide % d) as u64;
}
rem
}
pub(crate) fn from_decimal_digits(digits: &[u8]) -> Vec<u64> {
let mut limbs = Vec::new();
for &digit in digits {
debug_assert!(digit.is_ascii_digit(), "not a decimal digit");
mul_small(&mut limbs, 10);
add_small(&mut limbs, u64::from(digit - b'0'));
}
trim(&mut limbs);
limbs
}
pub(crate) fn cmp(a: &[u64], b: &[u64]) -> Ordering {
a.len()
.cmp(&b.len())
.then_with(|| a.iter().rev().cmp(b.iter().rev()))
}
pub(crate) fn bit_len(limbs: &[u64]) -> u64 {
match limbs.last() {
None => 0,
Some(&top) => (limbs.len() as u64 - 1) * 64 + (64 - u64::from(top.leading_zeros())),
}
}
pub(crate) fn trailing_zeros(limbs: &[u64]) -> u64 {
for (index, &limb) in limbs.iter().enumerate() {
if limb != 0 {
return index as u64 * 64 + u64::from(limb.trailing_zeros());
}
}
0
}
pub(crate) fn significant_bits(limbs: &[u64]) -> u64 {
bit_len(limbs) - trailing_zeros(limbs)
}
pub(crate) fn from_u64(x: u64) -> Vec<u64> {
if x == 0 {
Vec::new()
} else {
vec![x]
}
}
pub(crate) fn shl(limbs: &mut Vec<u64>, n: u64) {
if is_zero(limbs) || n == 0 {
return;
}
let bit_shift = (n % 64) as u32;
if bit_shift != 0 {
let mut carry: u64 = 0;
for limb in limbs.iter_mut() {
let carried_out = *limb >> (64 - bit_shift);
*limb = (*limb << bit_shift) | carry;
carry = carried_out;
}
if carry != 0 {
limbs.push(carry);
}
}
let limb_shift = (n / 64) as usize;
if limb_shift != 0 {
limbs.splice(0..0, std::iter::repeat_n(0, limb_shift));
}
}
pub(crate) fn mul_pow5(limbs: &mut Vec<u64>, mut n: u64) {
while n >= POW5_CHUNK_EXP {
mul_small(limbs, POW5_CHUNK);
n -= POW5_CHUNK_EXP;
}
if n != 0 {
mul_small(limbs, 5u64.pow(n as u32));
}
}
pub(crate) fn mul_pow10(limbs: &mut Vec<u64>, n: u64) {
mul_pow5(limbs, n);
shl(limbs, n);
}
pub(crate) fn to_decimal_digits(limbs: &[u64]) -> Vec<u8> {
if is_zero(limbs) {
return vec![b'0'];
}
let mut rest = limbs.to_vec();
let mut chunks = Vec::new();
while !is_zero(&rest) {
chunks.push(div_small(&mut rest, POW10_CHUNK));
}
let mut digits = chunks.pop().expect("non-zero").to_string().into_bytes();
while let Some(chunk) = chunks.pop() {
digits.extend_from_slice(
format!("{:0width$}", chunk, width = POW10_CHUNK_EXP as usize).as_bytes(),
);
}
digits
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_normalised(limbs: &[u64]) {
assert_ne!(limbs.last(), Some(&0), "leading zero limb: {:?}", limbs);
}
fn mag(decimal: &str) -> Vec<u64> {
let limbs = from_decimal_digits(decimal.as_bytes());
assert_normalised(&limbs);
limbs
}
fn to_decimal(limbs: &[u64]) -> String {
String::from_utf8(to_decimal_digits(limbs)).unwrap()
}
#[test]
fn decimal_digits_round_trip() {
for value in [
"0",
"1",
"9",
"18446744073709551615", "18446744073709551616", "340282366920938463463374607431768211455", "340282366920938463463374607431768211456", "123456789012345678901234567890123456789012345678901234567890",
] {
let limbs = mag(value);
assert_eq!(to_decimal(&limbs), value, "round trip of {}", value);
}
}
#[test]
fn zero_is_the_empty_magnitude() {
assert!(is_zero(&mag("0")));
assert!(is_zero(&mag("0000")));
assert_eq!(mag("0"), Vec::<u64>::new());
assert_eq!(bit_len(&mag("0")), 0);
}
#[test]
fn mul_and_div_by_small_are_inverse() {
let mut limbs = mag("123456789012345678901234567890");
mul_small(&mut limbs, 10);
assert_normalised(&limbs);
assert_eq!(to_decimal(&limbs), "1234567890123456789012345678900");
assert_eq!(div_small(&mut limbs, TEN), 0);
assert_normalised(&limbs);
assert_eq!(to_decimal(&limbs), "123456789012345678901234567890");
}
#[test]
fn div_small_reports_the_remainder() {
let mut limbs = mag("18446744073709551617"); assert_eq!(div_small(&mut limbs, TEN), 7);
assert_eq!(to_decimal(&limbs), "1844674407370955161");
let limbs = mag("18446744073709551617");
assert_eq!(rem_small(&limbs, TEN), 7);
assert_eq!(to_decimal(&limbs), "18446744073709551617");
}
#[test]
fn multiplying_by_zero_normalises_to_zero() {
let mut limbs = mag("123456789012345678901234567890");
mul_small(&mut limbs, 0);
assert!(is_zero(&limbs));
assert_normalised(&limbs);
}
#[test]
fn add_small_carries_across_limbs() {
let mut limbs = mag("18446744073709551615");
add_small(&mut limbs, 1);
assert_normalised(&limbs);
assert_eq!(to_decimal(&limbs), "18446744073709551616");
assert_eq!(limbs, vec![0, 1]);
let mut limbs = mag("0");
add_small(&mut limbs, 7);
assert_eq!(limbs, vec![7]);
}
#[test]
fn cmp_orders_by_magnitude() {
assert_eq!(cmp(&mag("0"), &mag("0")), Ordering::Equal);
assert_eq!(cmp(&mag("0"), &mag("1")), Ordering::Less);
assert_eq!(
cmp(&mag("18446744073709551616"), &mag("18446744073709551615")),
Ordering::Greater
);
assert_eq!(
cmp(
&mag("340282366920938463463374607431768211455"),
&mag("340282366920938463463374607431768211454")
),
Ordering::Greater
);
assert_eq!(
cmp(&mag("123456789012345678901"), &mag("123456789012345678901")),
Ordering::Equal
);
}
#[test]
fn bit_len_brackets_the_magnitude() {
assert_eq!(bit_len(&mag("0")), 0);
assert_eq!(bit_len(&mag("1")), 1);
assert_eq!(bit_len(&mag("255")), 8);
assert_eq!(bit_len(&mag("18446744073709551615")), 64); assert_eq!(bit_len(&mag("18446744073709551616")), 65); }
#[test]
fn significant_bits_ignores_factors_of_two() {
assert_eq!(significant_bits(&mag("1")), 1);
assert_eq!(significant_bits(&mag("2")), 1);
assert_eq!(significant_bits(&mag("3")), 2);
assert_eq!(significant_bits(&mag("18446744073709551616")), 1); assert_eq!(
significant_bits(&mag("1267650600228229401496703205376")), 1
);
let mut ten_pow_30 = mag("1");
mul_pow10(&mut ten_pow_30, 30);
assert_eq!(significant_bits(&ten_pow_30), 70);
assert_eq!(significant_bits(&mag("18446744073709551617")), 65);
}
#[test]
fn from_u64_normalises_zero() {
assert_eq!(from_u64(0), Vec::<u64>::new());
assert_eq!(from_u64(7), vec![7]);
assert_eq!(from_u64(u64::MAX), vec![u64::MAX]);
}
#[test]
fn shl_shifts_across_limb_boundaries() {
let mut limbs = mag("1");
shl(&mut limbs, 3);
assert_eq!(limbs, vec![8]);
let mut limbs = mag("1");
shl(&mut limbs, 64);
assert_eq!(limbs, vec![0, 1]);
assert_eq!(to_decimal(&limbs), "18446744073709551616");
let mut limbs = mag("1");
shl(&mut limbs, 65);
assert_eq!(limbs, vec![0, 2]);
let mut limbs = mag("18446744073709551615"); shl(&mut limbs, 1);
assert_normalised(&limbs);
assert_eq!(to_decimal(&limbs), "36893488147419103230");
let mut limbs = mag("0");
shl(&mut limbs, 130);
assert!(is_zero(&limbs));
assert_normalised(&limbs);
}
#[test]
fn mul_pow10_matches_repeated_multiplication() {
for n in [0u64, 1, 5, 18, 19, 20, 27, 28, 60] {
let mut scaled = mag("123456789");
mul_pow10(&mut scaled, n);
assert_normalised(&scaled);
let mut oracle = mag("123456789");
for _ in 0..n {
mul_small(&mut oracle, 10);
}
assert_eq!(scaled, oracle, "123456789 * 10^{}", n);
}
let mut power = mag("1");
mul_pow10(&mut power, 30);
assert_eq!(to_decimal(&power), format!("1{}", "0".repeat(30)));
}
#[test]
fn to_decimal_digits_pads_the_lower_chunks() {
assert_eq!(to_decimal_digits(&mag("0")), b"0");
assert_eq!(to_decimal_digits(&mag("1")), b"1");
let mut power = mag("1");
mul_pow10(&mut power, 19);
assert_eq!(to_decimal(&power), "10000000000000000000");
assert_eq!(
to_decimal(&mag("10000000000000000001")), "10000000000000000001"
);
}
}