#![allow(dead_code)]
fn low_u64(x: u128) -> u64 {
(x & u128::from(u64::MAX))
.try_into()
.expect("masked to u64 range")
}
fn low_u64_from_i128(x: i128) -> u64 {
u64::try_from(x).expect("value must fit u64")
}
const MAX_LIMBS: usize = 24;
#[derive(Clone, Copy)]
struct BigUint {
limbs: [u64; MAX_LIMBS],
len: usize,
}
impl BigUint {
const fn zero() -> Self {
Self {
limbs: [0; MAX_LIMBS],
len: 1,
}
}
const fn one() -> Self {
let mut b = Self::zero();
b.limbs[0] = 1;
b
}
const fn from_u64(v: u64) -> Self {
let mut b = Self::zero();
b.limbs[0] = v;
if v > 0 {
b.len = 1;
}
b
}
fn mul_u64(&self, rhs: u64) -> Self {
let mut result = Self::zero();
let mut carry = 0u128;
for i in 0..self.len {
carry += u128::from(self.limbs[i]) * u128::from(rhs);
result.limbs[i] = low_u64(carry);
carry >>= 64;
}
result.len = self.len;
if carry > 0 {
result.limbs[result.len] = low_u64(carry);
result.len += 1;
}
result
}
fn shl(&self, shift: u32) -> Self {
if shift == 0 {
return *self;
}
let word_shift = (shift / 64) as usize;
let bit_shift = shift % 64;
let mut result = Self::zero();
if bit_shift == 0 {
for i in 0..self.len {
result.limbs[i + word_shift] = self.limbs[i];
}
result.len = self.len + word_shift;
} else {
let mut carry = 0u64;
for i in 0..self.len {
result.limbs[i + word_shift] = (self.limbs[i] << bit_shift) | carry;
carry = self.limbs[i] >> (64 - bit_shift);
}
result.len = self.len + word_shift;
if carry > 0 {
result.limbs[result.len] = carry;
result.len += 1;
}
}
result
}
fn shr(&self, shift: u32) -> Self {
if shift == 0 {
return *self;
}
let word_shift = (shift / 64) as usize;
let bit_shift = shift % 64;
let mut result = Self::zero();
if word_shift >= self.len {
return result;
}
if bit_shift == 0 {
for i in word_shift..self.len {
result.limbs[i - word_shift] = self.limbs[i];
}
} else {
for i in word_shift..self.len {
result.limbs[i - word_shift] = self.limbs[i] >> bit_shift;
if i + 1 < self.len {
result.limbs[i - word_shift] |= self.limbs[i + 1] << (64 - bit_shift);
}
}
}
result.len = self.len - word_shift;
while result.len > 1 && result.limbs[result.len - 1] == 0 {
result.len -= 1;
}
result
}
fn bit_length(&self) -> u32 {
if self.len == 0 {
return 0;
}
let top = self.limbs[self.len - 1];
if top == 0 {
return 0;
}
let len_u32 = u32::try_from(self.len).expect("len bounded by MAX_LIMBS");
len_u32 * 64 - top.leading_zeros()
}
const fn is_zero(&self) -> bool {
self.len == 0 || (self.len == 1 && self.limbs[0] == 0)
}
fn upper_128(&self) -> u128 {
let bl = self.bit_length();
if bl <= 128 {
let lo = u128::from(self.limbs[0]);
let hi = if self.len > 1 {
u128::from(self.limbs[1]) << 64
} else {
0
};
return lo | hi;
}
let shifted = self.shr(bl - 128);
let lo = u128::from(shifted.limbs[0]);
let hi = if shifted.len > 1 {
u128::from(shifted.limbs[1]) << 64
} else {
0
};
lo | hi
}
fn add_u64(&self, rhs: u64) -> Self {
let mut result = *self;
let mut carry = u128::from(rhs);
for i in 0..result.len {
carry += u128::from(result.limbs[i]);
result.limbs[i] = low_u64(carry);
carry >>= 64;
if carry == 0 {
break;
}
}
if carry > 0 {
result.limbs[result.len] = low_u64(carry);
result.len += 1;
}
result
}
const fn bit(&self, idx: u32) -> bool {
let word = (idx / 64) as usize;
let bit = idx % 64;
if word >= self.len {
return false;
}
(self.limbs[word] >> bit) & 1 == 1
}
}
fn pow5(n: u32) -> BigUint {
let mut result = BigUint::one();
for _ in 0..n {
result = result.mul_u64(5);
}
result
}
fn floor_log2_pow10(f: i32) -> i32 {
let product = (i64::from(f) * 217_706) >> 16;
i32::try_from(product).expect("|f| < 1700 keeps product within i32 range")
}
pub fn compute_multiplier(f: i32) -> (u64, u64) {
if f == 0 {
return (1, 1u64 << 63); }
let abs_f = f.unsigned_abs();
let p5 = pow5(abs_f);
let bl5 = p5.bit_length();
if f > 0 {
let shift = 127 + bl5;
let numerator = BigUint::one().shl(shift);
let m_128 = bigdiv_ceil_128(&numerator, &p5);
(low_u64(m_128), low_u64(m_128 >> 64))
} else if bl5 <= 128 {
let shifted = p5.shl(128 - bl5);
let lo = shifted.limbs[0];
let hi = if shifted.len > 1 { shifted.limbs[1] } else { 0 };
let m = (u128::from(hi) << 64 | u128::from(lo)) + 1;
(low_u64(m), low_u64(m >> 64))
} else {
let discard = bl5 - 128;
let shifted = p5.shr(discard);
let lo = shifted.limbs[0];
let hi = if shifted.len > 1 { shifted.limbs[1] } else { 0 };
let m = u128::from(hi) << 64 | u128::from(lo);
let check = shifted.shl(discard);
let needs_ceil = bigcmp(&check, &p5) < 0;
let m = if needs_ceil { m + 1 } else { m };
(low_u64(m), low_u64(m >> 64))
}
}
fn bigdiv_ceil_128(numerator: &BigUint, divisor: &BigUint) -> u128 {
let num_bits = numerator.bit_length();
let div_bits = divisor.bit_length();
if num_bits < div_bits {
return 1; }
let quot_bits = num_bits - div_bits + 1;
assert!(quot_bits <= 129, "quotient too large for u128");
let mut remainder = *numerator;
let mut quotient = 0u128;
for i in (0..quot_bits).rev() {
let shifted_div = divisor.shl(i);
if bigcmp(&remainder, &shifted_div) >= 0 {
remainder = bigsub(&remainder, &shifted_div);
if i < 128 {
quotient |= 1u128 << i;
}
}
}
if !remainder.is_zero() {
quotient += 1;
}
quotient
}
fn bigcmp(a: &BigUint, b: &BigUint) -> i32 {
let alen = effective_len(a);
let blen = effective_len(b);
if alen != blen {
return if alen > blen { 1 } else { -1 };
}
for i in (0..alen).rev() {
if a.limbs[i] != b.limbs[i] {
return if a.limbs[i] > b.limbs[i] { 1 } else { -1 };
}
}
0
}
const fn effective_len(a: &BigUint) -> usize {
let mut l = a.len;
while l > 0 && a.limbs[l - 1] == 0 {
l -= 1;
}
l
}
fn bigsub(a: &BigUint, b: &BigUint) -> BigUint {
let mut result = *a;
let mut borrow = 0i128;
for i in 0..a.len {
let ai = i128::from(a.limbs[i]);
let bi = if i < b.len { i128::from(b.limbs[i]) } else { 0 };
let diff = ai - bi - borrow;
if diff < 0 {
result.limbs[i] = low_u64_from_i128(diff + (1i128 << 64));
borrow = 1;
} else {
result.limbs[i] = low_u64_from_i128(diff);
borrow = 0;
}
}
while result.len > 1 && result.limbs[result.len - 1] == 0 {
result.len -= 1;
}
result
}
pub fn compute_minverse(n: u32) -> (u64, u64) {
if n == 0 {
return (1, u64::MAX);
}
let mut p5 = 1u64;
for _ in 0..n {
p5 = p5.wrapping_mul(5);
}
let mut inv = p5; for _ in 0..6 {
inv = inv.wrapping_mul(2u64.wrapping_sub(p5.wrapping_mul(inv)));
}
debug_assert!(p5.wrapping_mul(inv) == 1, "modular inverse check failed");
let bound = u64::MAX / p5;
(inv, bound)
}
pub const STORAGE_INDEX_OFFSET: i32 = -324;
pub const TABLE_LEN: usize = 617;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pow5_small_values() {
assert_eq!(pow5(0).limbs[0], 1);
assert_eq!(pow5(1).limbs[0], 5);
assert_eq!(pow5(2).limbs[0], 25);
assert_eq!(pow5(10).limbs[0], 9_765_625);
assert_eq!(pow5(13).limbs[0], 1_220_703_125);
assert_eq!(pow5(27).limbs[0], 7_450_580_596_923_828_125);
assert_eq!(pow5(27).len, 1);
assert!(pow5(28).len > 1);
}
#[test]
fn pow5_bit_length() {
assert_eq!(pow5(1).bit_length(), 3);
assert_eq!(pow5(10).bit_length(), 24);
assert_eq!(pow5(27).bit_length(), 63);
let bl = pow5(324).bit_length();
assert!((752..=754).contains(&bl), "5^324 bit_length = {bl}");
}
#[test]
fn multiplier_f0() {
let (lo, hi) = compute_multiplier(0);
assert_eq!((lo, hi), (1, 0x8000_0000_0000_0000));
}
#[test]
fn multiplier_f_neg1() {
let (lo, hi) = compute_multiplier(-1);
assert_eq!(lo, 1);
assert_eq!(hi, 0xa000_0000_0000_0000);
}
#[test]
fn multiplier_f1() {
let (lo, hi) = compute_multiplier(1);
let m = u128::from(hi) << 64 | u128::from(lo);
assert!(m > 0);
let top_bit = 128 - m.leading_zeros();
assert_eq!(
top_bit, 128,
"should be normalized to 128 bits, got {top_bit}"
);
}
#[test]
fn minverse_basic() {
let (inv, bound) = compute_minverse(0);
assert_eq!(inv, 1);
assert_eq!(bound, u64::MAX);
let (inv1, bound1) = compute_minverse(1);
assert_eq!(inv1.wrapping_mul(5), 1);
assert_eq!(bound1, u64::MAX / 5);
assert!(10u64.wrapping_mul(inv1) <= bound1);
assert!(7u64.wrapping_mul(inv1) > bound1);
}
#[test]
fn minverse_all_27() {
for n in 0..=26u32 {
let (inv, bound) = compute_minverse(n);
let p5 = {
let mut v = 1u64;
for _ in 0..n {
v = v.wrapping_mul(5);
}
v
};
if n > 0 {
assert_eq!(inv.wrapping_mul(p5), 1, "inverse failed for n={n}");
}
if n > 0 {
assert!(
p5.wrapping_mul(inv) <= bound,
"5^{n} should be multiple of 5^{n}"
);
}
if n > 0 && n < 27 {
assert!(
(p5 + 1).wrapping_mul(inv) > bound,
"5^{n}+1 should not be multiple of 5^{n}"
);
}
}
}
#[test]
fn generate_full_multiplier_table() {
for i in 0..TABLE_LEN {
let f = i32::try_from(i).expect("TABLE_LEN fits i32") + STORAGE_INDEX_OFFSET;
let (lo, hi) = compute_multiplier(f);
let m = u128::from(hi) << 64 | u128::from(lo);
assert!(
m >= (1u128 << 127),
"multiplier for f={f} not normalized: hi={hi:#x}, lo={lo:#x}, m has {} bits",
128 - m.leading_zeros()
);
let _ = m;
}
}
#[test]
fn multiplier_cross_check_c_reference() {
let (lo, hi) = compute_multiplier(-324);
let m = u128::from(hi) << 64 | u128::from(lo);
assert!(m >= (1u128 << 127), "f=-324 not normalized");
let (lo, hi) = compute_multiplier(-1);
assert_eq!(lo, 1);
assert_eq!(hi, 0xa000_0000_0000_0000);
let (lo, hi) = compute_multiplier(0);
assert_eq!(lo, 1);
assert_eq!(hi, 0x8000_0000_0000_0000);
let (lo, hi) = compute_multiplier(1);
let m = u128::from(hi) << 64 | u128::from(lo);
assert!(m >= (1u128 << 127), "f=1 not normalized");
assert_eq!(m.leading_zeros(), 0, "f=1 top bit not set");
let (lo, hi) = compute_multiplier(292);
let m = u128::from(hi) << 64 | u128::from(lo);
assert!(m >= (1u128 << 127), "f=292 not normalized");
}
#[test]
fn print_multiplier_table() {
for i in 0..TABLE_LEN {
let f = i32::try_from(i).expect("TABLE_LEN fits i32") + STORAGE_INDEX_OFFSET;
let (lo, hi) = compute_multiplier(f);
let m = u128::from(hi) << 64 | u128::from(lo);
assert!(m >= (1u128 << 127), "f={f} not normalized: {m:#034x}");
}
}
#[test]
fn print_minverse_table() {
for n in 0..=26u32 {
let (inv, bound) = compute_minverse(n);
if n > 0 {
let p5: u64 = (0..n).fold(1u64, |a, _| a.wrapping_mul(5));
assert_eq!(inv.wrapping_mul(p5), 1, "inverse broken for n={n}");
assert!(p5.wrapping_mul(inv) <= bound, "5^{n} not detected");
}
}
}
#[test]
fn multiplier_matches_c_reference_spot_checks() {
let cases: &[(i32, u64, u64)] = &[
(-18, 0x0000_0000_0000_0001, 0xde0b_6b3a_7640_0000),
(-17, 0x0000_0000_0000_0001, 0xb1a2_bc2e_c500_0000),
(-16, 0x0000_0000_0000_0001, 0x8e1b_c9bf_0400_0000),
(-15, 0x0000_0000_0000_0001, 0xe35f_a931_a000_0000),
(-14, 0x0000_0000_0000_0001, 0xb5e6_20f4_8000_0000),
(-1, 1, 0xa000_0000_0000_0000),
(0, 1, 0x8000_0000_0000_0000),
];
for &(f, exp_lo, exp_hi) in cases {
let (lo, hi) = compute_multiplier(f);
assert_eq!(
(lo, hi),
(exp_lo, exp_hi),
"mismatch for f={f}: got ({lo:#018x}, {hi:#018x}), \
expected ({exp_lo:#018x}, {exp_hi:#018x})"
);
}
}
#[test]
fn mshift_sanity() {
fn mshift(m: u64, upper: u64, lower: u64) -> u64 {
let hi = u128::from(m) * u128::from(upper);
let lo = u128::from(m) * u128::from(lower);
((hi + (lo >> 64)) >> 64) as u64
}
let r = mshift(1u64 << 63, 1u64 << 63, 0);
assert_eq!(r, 1u64 << 62);
let r = mshift(1u64 << 63, 0, 1u64 << 63);
assert_eq!(r, 0);
}
#[test]
fn emit_rust_tables() {
use core::fmt::Write as _;
let mut out = String::new();
out.push_str("const MULTIPLIERS: [(u64, u64); 617] = [\n");
for i in 0..TABLE_LEN {
let f = i32::try_from(i).expect("TABLE_LEN fits i32") + STORAGE_INDEX_OFFSET;
let (lo, hi) = compute_multiplier(f);
writeln!(&mut out, " (0x{lo:016x}, 0x{hi:016x}),").expect("write to String");
}
out.push_str("];\n\n");
out.push_str("const MINVERSE: [(u64, u64); 27] = [\n");
for n in 0..=26u32 {
let (inv, bound) = compute_minverse(n);
writeln!(&mut out, " (0x{inv:016x}, 0x{bound:016x}),").expect("write to String");
}
out.push_str("];\n");
assert!(out.len() > 10_000, "table source too short");
#[cfg(feature = "std")]
{
std::fs::write(
concat!(env!("CARGO_MANIFEST_DIR"), "/src/teju_tables.txt"),
&out,
)
.expect("failed to write tables file");
}
}
}