use crate::AmountErrorKind;
use crate::AmountSign;
use crate::Rounding;
use core::cmp::Ordering;
#[inline]
pub(crate) const fn upow10(pow: u32) -> u64 {
const P10: [u64; 20] = [
1,
10,
100,
1_000,
10_000,
100_000,
1_000_000,
10_000_000,
100_000_000,
1_000_000_000,
10_000_000_000,
100_000_000_000,
1_000_000_000_000,
10_000_000_000_000,
100_000_000_000_000,
1_000_000_000_000_000,
10_000_000_000_000_000,
100_000_000_000_000_000,
1_000_000_000_000_000_000,
10_000_000_000_000_000_000,
];
P10[pow as usize]
}
#[cfg(all(target_arch = "x86_64", feature = "asm"))]
#[inline]
pub(crate) fn div_2by1(hi: u64, lo: u64, d: u64) -> (u64, u64) {
debug_assert!(hi < d);
let q: u64;
let r: u64;
unsafe {
core::arch::asm!(
"div {d}",
d = in(reg) d,
inout("rax") lo => q,
inout("rdx") hi => r,
options(pure, nomem, nostack)
);
}
(q, r)
}
#[cfg(any(not(target_arch = "x86_64"), not(feature = "asm")))]
fn udiv_qrnnd(n1: u64, n0: u64, d: u64) -> (u64, u64) {
debug_assert!(d >= 1 << 63 && n1 < d);
let d_high = d >> 32;
let d_low = d & 0xffff_ffff;
let mut q_high = n1 / d_high;
let mut r_high = n1 - q_high * d_high;
let m = q_high * d_low;
r_high = (r_high << 32) | (n0 >> 32);
if r_high < m {
q_high -= 1;
r_high = r_high.wrapping_add(d);
if r_high >= d && r_high < m {
q_high -= 1;
r_high = r_high.wrapping_add(d);
}
}
r_high = r_high.wrapping_sub(m);
let mut q_low = r_high / d_high;
let mut r_low = r_high - q_low * d_high;
let m = q_low * d_low;
r_low = (r_low << 32) | (n0 & 0xffff_ffff);
if r_low < m {
q_low -= 1;
r_low = r_low.wrapping_add(d);
if r_low >= d && r_low < m {
q_low -= 1;
r_low = r_low.wrapping_add(d);
}
}
r_low = r_low.wrapping_sub(m);
((q_high << 32) | q_low, r_low)
}
#[cfg(any(not(target_arch = "x86_64"), not(feature = "asm")))]
#[inline]
pub(crate) fn div_2by1(hi: u64, lo: u64, d: u64) -> (u64, u64) {
debug_assert!(hi < d);
if hi == 0 {
return (lo / d, lo % d);
}
let s = d.leading_zeros();
if s == 0 {
udiv_qrnnd(hi, lo, d)
} else {
let (q, r) = udiv_qrnnd((hi << s) | (lo >> (64 - s)), lo << s, d << s);
(q, r >> s)
}
}
#[inline]
pub(crate) fn div_words_by_word(u: &mut [u64], d: u64) -> u64 {
debug_assert!(d != 0);
let mut rem: u64 = 0;
for x in u.iter_mut().rev() {
if rem == 0 && *x < d {
rem = *x;
*x = 0;
continue;
}
let (q, r) = div_2by1(rem, *x, d);
*x = q;
rem = r;
}
rem
}
const fn pow10_norm(digits: u32) -> (u64, u32, u64) {
let d = upow10(digits);
let s = d.leading_zeros();
let dn = d << s;
let v = (u128::MAX / (dn as u128) - (1u128 << 64)) as u64;
(dn, s, v)
}
#[inline]
const fn div_2by1_recip(u1: u64, u0: u64, dn: u64, v: u64) -> (u64, u64) {
let q = (v as u128) * (u1 as u128) + (((u1 as u128) << 64) | u0 as u128);
let mut q1 = ((q >> 64) as u64).wrapping_add(1);
let q0 = q as u64;
let mut r = u0.wrapping_sub(q1.wrapping_mul(dn));
let over = (r > q0) as u64;
q1 = q1.wrapping_sub(over);
r = r.wrapping_add(dn & over.wrapping_neg());
if r >= dn {
q1 += 1;
r -= dn;
}
(q1, r)
}
#[inline]
const fn div_2by1_pow10<const DIGITS: u8>(rem: u64, limb: u64) -> (u64, u64) {
let (dn, s, v) = const { pow10_norm(DIGITS as u32) };
let u1 = if s == 0 {
rem
} else {
(rem << s) | (limb >> (64 - s))
};
let (q, r) = div_2by1_recip(u1, limb << s, dn, v);
(q, r >> s)
}
#[inline]
pub(crate) const fn div_words_by_pow10<const DIGITS: u8>(u: &mut [u64]) -> u64 {
if DIGITS == 0 {
return 0; }
let d = const { upow10(DIGITS as u32) };
let mut rem: u64 = 0;
let mut i = u.len();
while i > 0 {
i -= 1;
if rem == 0 && u[i] < d {
rem = u[i];
u[i] = 0;
continue;
}
let (q, r) = div_2by1_pow10::<DIGITS>(rem, u[i]);
u[i] = q;
rem = r;
}
rem
}
#[inline]
pub(crate) const fn div_rem_u128_pow10<const DIGITS: u8>(n: u128) -> (u128, u64) {
if DIGITS == 0 {
return (n, 0);
}
let d = const { upow10(DIGITS as u32) };
let hi = (n >> 64) as u64;
let lo = n as u64;
let (q1, r1) = if hi < d {
(0, hi) } else {
div_2by1_pow10::<DIGITS>(0, hi)
};
let (q0, r) = div_2by1_pow10::<DIGITS>(r1, lo);
(((q1 as u128) << 64) | q0 as u128, r)
}
#[inline]
pub(crate) const fn mul_add_word(acc: &mut [u64], mul: u64, add: u64) -> bool {
let mut carry: u128 = add as u128;
let mut i = 0;
while i < acc.len() {
let v = (acc[i] as u128) * (mul as u128) + carry;
acc[i] = v as u64;
carry = v >> 64;
i += 1;
}
carry != 0
}
#[inline]
pub(crate) const fn mul_words(c: &mut [u64], a: &[u64], b: &[u64]) {
debug_assert!(c.len() >= a.len() + b.len());
let mut i = 0;
while i < c.len() {
c[i] = 0;
i += 1;
}
let mut i = 0;
while i < a.len() {
let mut carry: u128 = 0;
let mut j = 0;
while j < b.len() {
let v = (a[i] as u128) * (b[j] as u128) + (c[i + j] as u128) + carry;
c[i + j] = v as u64;
carry = v >> 64;
j += 1;
}
c[i + b.len()] = carry as u64;
i += 1;
}
}
#[inline]
pub(crate) fn sig_limbs(l: &[u64]) -> usize {
let mut n = l.len();
while n > 1 && l[n - 1] == 0 {
n -= 1;
}
n
}
#[inline]
pub(crate) const fn is_zero(l: &[u64]) -> bool {
let mut i = 0;
while i < l.len() {
if l[i] != 0 {
return false;
}
i += 1;
}
true
}
#[inline]
pub(crate) fn cmp_words(a: &[u64], b: &[u64]) -> Ordering {
debug_assert_eq!(a.len(), b.len());
for i in (0..a.len()).rev() {
match a[i].cmp(&b[i]) {
Ordering::Equal => {}
o => return o,
}
}
Ordering::Equal
}
#[inline]
pub(crate) fn shl1(l: &mut [u64]) -> u64 {
let mut carry = 0u64;
for x in l.iter_mut() {
let next = *x >> 63;
*x = (*x << 1) | carry;
carry = next;
}
carry
}
pub(crate) const KNUTH_MAX_M: usize = 8;
#[inline]
pub(crate) fn div_knuth(q: &mut [u64], r: &mut [u64], u: &[u64], v: &[u64]) {
let m = u.len();
let n = v.len();
debug_assert!((2..=m).contains(&n), "use div_words_by_word for n == 1");
debug_assert!(v[n - 1] != 0);
debug_assert!(m <= KNUTH_MAX_M);
debug_assert!(q.len() > m - n && r.len() >= n);
let mut un = [0u64; KNUTH_MAX_M + 1]; let mut vn = [0u64; KNUTH_MAX_M];
let s = v[n - 1].leading_zeros();
if s == 0 {
vn[..n].copy_from_slice(v);
un[..m].copy_from_slice(u);
un[m] = 0;
} else {
for i in (1..n).rev() {
vn[i] = (v[i] << s) | (v[i - 1] >> (64 - s));
}
vn[0] = v[0] << s;
un[m] = u[m - 1] >> (64 - s);
for i in (1..m).rev() {
un[i] = (u[i] << s) | (u[i - 1] >> (64 - s));
}
un[0] = u[0] << s;
}
for x in q.iter_mut() {
*x = 0;
}
const B: u128 = 1u128 << 64;
let vtop = vn[n - 1];
let vnext = vn[n - 2];
let mut j = m - n;
loop {
debug_assert!(un[j + n] <= vtop);
let (mut qhat, mut rhat): (u128, u128) = if un[j + n] == vtop {
(
B + (un[j + n - 1] / vtop) as u128,
(un[j + n - 1] % vtop) as u128,
)
} else {
let (q0, r0) = div_2by1(un[j + n], un[j + n - 1], vtop);
(q0 as u128, r0 as u128)
};
loop {
if qhat >= B || qhat * (vnext as u128) > ((rhat << 64) | un[j + n - 2] as u128) {
qhat -= 1;
rhat += vtop as u128;
if rhat < B {
continue;
}
}
break;
}
let mut qd = qhat as u64;
let mut borrow: i128 = 0;
for i in 0..n {
let p = (qd as u128) * (vn[i] as u128);
let t = (un[i + j] as i128) - borrow - ((p as u64) as i128);
un[i + j] = t as u64;
borrow = ((p >> 64) as i128) - (t >> 64);
}
let t = (un[j + n] as i128) - borrow;
un[j + n] = t as u64;
if t < 0 {
qd -= 1;
let mut carry: u128 = 0;
for i in 0..n {
let sum = un[i + j] as u128 + vn[i] as u128 + carry;
un[i + j] = sum as u64;
carry = sum >> 64;
}
un[j + n] = un[j + n].wrapping_add(carry as u64);
}
q[j] = qd;
if j == 0 {
break;
}
j -= 1;
}
if s == 0 {
r[..n].copy_from_slice(&un[..n]);
} else {
for i in 0..n - 1 {
r[i] = (un[i] >> s) | (un[i + 1] << (64 - s));
}
r[n - 1] = un[n - 1] >> s;
}
for x in r.iter_mut().skip(n) {
*x = 0;
}
}
#[inline]
pub(crate) const fn dec_mul<const DIGITS: u8, const W: usize, const W2: usize>(
a_neg: bool,
a_mag: &[u64; W],
b_neg: bool,
b_mag: &[u64; W],
mode: Rounding,
) -> Option<(bool, [u64; W])> {
debug_assert!(W2 == 2 * W);
if W > 2 && is_zero(a_mag.split_at(1).1) && is_zero(b_mag.split_at(1).1) {
let a2 = [a_mag[0], 0];
let b2 = [b_mag[0], 0];
if let Some((n2, m2)) = dec_mul::<DIGITS, 2, 4>(a_neg, &a2, b_neg, &b2, mode) {
let mut mag = [0u64; W];
mag[0] = m2[0];
mag[1] = m2[1];
return Some((n2, mag));
}
}
let neg = a_neg != b_neg;
let mut prod = [0u64; W2];
mul_words(&mut prod, a_mag, b_mag);
let half_up = matches!(mode, Rounding::HalfUp);
if half_up {
mul_add_word(&mut prod, 1, const { upow10(DIGITS as u32) / 2 });
}
let rem = div_words_by_pow10::<DIGITS>(&mut prod);
if !is_zero(prod.split_at(W).1) {
return None;
}
let mut mag = [0u64; W];
let mut i = 0;
while i < W {
mag[i] = prod[i];
i += 1;
}
if !half_up
&& round_up_by_cmp(
cmp_twice_rem_u64(rem, const { upow10(DIGITS as u32) }),
rem == 0,
mag[0] & 1 != 0,
neg,
mode,
)
&& mul_add_word(&mut mag, 1, 1)
{
return None;
}
if mag[W - 1] >> 63 != 0 {
return None;
}
Some((neg, mag))
}
#[inline]
pub(crate) fn dec_div<const DIGITS: u8, const W: usize, const WP1: usize>(
a_neg: bool,
a_mag: &[u64; W],
b_neg: bool,
b_mag: &[u64; W],
mode: Rounding,
) -> Option<(bool, [u64; W])> {
debug_assert!(WP1 == W + 1);
if is_zero(b_mag) {
return None;
}
if W > 2 && is_zero(a_mag.split_at(2).1) && is_zero(b_mag.split_at(2).1) {
let a2 = [a_mag[0], a_mag[1]];
let b2 = [b_mag[0], b_mag[1]];
if let Some((n2, m2)) = dec_div::<DIGITS, 2, 3>(a_neg, &a2, b_neg, &b2, mode) {
let mut mag = [0u64; W];
mag[0] = m2[0];
mag[1] = m2[1];
return Some((n2, mag));
}
}
let neg = a_neg != b_neg;
let mut num = [0u64; WP1];
mul_words(&mut num, a_mag, &[const { upow10(DIGITS as u32) }]);
let (rem_cmp, rem_zero) = div_mag_in_place(&mut num, b_mag);
round_quotient::<W>(&num, rem_cmp, rem_zero, neg, mode)
}
#[inline(always)]
fn div_mag_in_place(num: &mut [u64], div: &[u64]) -> (Ordering, bool) {
let n = sig_limbs(div);
if n == 1 {
let d = div[0];
let rem = div_words_by_word(num, d);
return (cmp_twice_rem_u64(rem, d), rem == 0);
}
let mut q = [0u64; KNUTH_MAX_M];
let mut r = [0u64; KNUTH_MAX_M];
let m = sig_limbs(num);
if m < n {
r[..m].copy_from_slice(&num[..m]);
num[..m].fill(0);
} else {
div_knuth(&mut q[..m - n + 1], &mut r[..n], &num[..m], &div[..n]);
num.copy_from_slice(&q[..num.len()]);
}
let rem_zero = is_zero(&r[..n]);
let carry = shl1(&mut r[..n]);
let cmp = if carry != 0 {
Ordering::Greater
} else {
cmp_words(&r[..n], &div[..n])
};
(cmp, rem_zero)
}
#[inline]
fn round_quotient<const W: usize>(
q: &[u64],
rem_cmp: Ordering,
rem_zero: bool,
neg: bool,
mode: Rounding,
) -> Option<(bool, [u64; W])> {
if !is_zero(q.split_at(W).1) {
return None;
}
let mut mag = [0u64; W];
mag.copy_from_slice(&q[..W]);
if round_up_by_cmp(rem_cmp, rem_zero, mag[0] & 1 != 0, neg, mode)
&& mul_add_word(&mut mag, 1, 1)
{
return None;
}
if mag[W - 1] >> 63 != 0 {
return None;
}
Some((neg, mag))
}
#[inline]
pub(crate) fn dec_mul_div<const W: usize, const W2: usize>(
a_neg: bool,
a_mag: &[u64; W],
b_neg: bool,
b_mag: &[u64; W],
c_neg: bool,
c_mag: &[u64; W],
mode: Rounding,
) -> Option<(bool, [u64; W])> {
debug_assert!(W2 == 2 * W);
if is_zero(c_mag) {
return None;
}
if W > 2
&& is_zero(a_mag.split_at(2).1)
&& is_zero(b_mag.split_at(2).1)
&& is_zero(c_mag.split_at(2).1)
{
let a2 = [a_mag[0], a_mag[1]];
let b2 = [b_mag[0], b_mag[1]];
let c2 = [c_mag[0], c_mag[1]];
if let Some((n2, m2)) = dec_mul_div::<2, 4>(a_neg, &a2, b_neg, &b2, c_neg, &c2, mode) {
let mut mag = [0u64; W];
mag[0] = m2[0];
mag[1] = m2[1];
return Some((n2, mag));
}
}
let neg = (a_neg != b_neg) != c_neg;
let mut prod = [0u64; W2];
mul_words(&mut prod, a_mag, b_mag);
let (rem_cmp, rem_zero) = div_mag_in_place(&mut prod, c_mag);
round_quotient::<W>(&prod, rem_cmp, rem_zero, neg, mode)
}
#[inline]
pub(crate) fn rem_mag<const W: usize>(a: &[u64; W], b: &[u64; W]) -> [u64; W] {
let n = sig_limbs(b);
let mut r = [0u64; W];
if n == 1 {
let mut q = *a;
r[0] = div_words_by_word(&mut q, b[0]);
return r;
}
if cmp_words(a, b) == Ordering::Less {
return *a;
}
let m = sig_limbs(a);
let mut q = [0u64; W];
div_knuth(&mut q[..m - n + 1], &mut r[..n], &a[..m], &b[..n]);
r
}
#[inline]
pub(crate) fn div_mag_by_word<const W: usize>(
neg: bool,
mag: &[u64; W],
d: u64,
mode: Rounding,
) -> [u64; W] {
let mut q = *mag;
let r = div_words_by_word(&mut q, d);
if round_up_by_cmp(cmp_twice_rem_u64(r, d), r == 0, q[0] & 1 != 0, neg, mode) {
mul_add_word(&mut q, 1, 1);
}
q
}
#[inline]
pub(crate) fn round_mag_to_step<const W: usize>(
neg: bool,
mag: &[u64; W],
step: u64,
mode: Rounding,
) -> [u64; W] {
let mut q = div_mag_by_word(neg, mag, step, mode);
let carry = mul_add_word(&mut q, step, 0);
debug_assert!(!carry);
q
}
#[inline]
pub(crate) fn split_mag_even<const W: usize>(mag: &[u64; W], n: u32) -> ([u64; W], [u64; W], u32) {
let mut low = *mag;
let r = div_words_by_word(&mut low, n as u64) as u32;
let mut high = low;
if r != 0 {
let carry = mul_add_word(&mut high, 1, 1);
debug_assert!(!carry);
}
(low, high, r)
}
#[inline]
pub(crate) const fn sqrt_round_up(mode: Rounding, rem_zero: bool, rem_gt_root: bool) -> bool {
match mode {
Rounding::Down => false,
Rounding::Up => !rem_zero,
Rounding::HalfUp | Rounding::HalfDown | Rounding::HalfEven => rem_gt_root,
}
}
#[inline]
pub(crate) const fn round_up_by_cmp(
twice_rem_vs_div: Ordering,
rem_is_zero: bool,
quo_is_odd: bool,
is_neg: bool,
mode: Rounding,
) -> bool {
if rem_is_zero {
return false;
}
match mode {
Rounding::HalfEven => {
matches!(twice_rem_vs_div, Ordering::Greater)
|| (matches!(twice_rem_vs_div, Ordering::Equal) && quo_is_odd)
}
Rounding::HalfUp => !matches!(twice_rem_vs_div, Ordering::Less),
Rounding::HalfDown => matches!(twice_rem_vs_div, Ordering::Greater),
Rounding::Down => is_neg,
Rounding::Up => !is_neg,
}
}
#[inline]
pub(crate) const fn cmp_twice_rem_u64(rem: u64, d: u64) -> Ordering {
debug_assert!(rem < d);
let other = d - rem;
if rem < other {
Ordering::Less
} else if rem > other {
Ordering::Greater
} else {
Ordering::Equal
}
}
pub const fn isqrt_f64(n: u128) -> u64 {
if n == 0 {
return 0;
}
debug_assert!(n >> 127 == 0);
let nf = n as f64;
let mut y = f64::from_bits(0x5FE6EB50C7B537A9u64.wrapping_sub(nf.to_bits() >> 1));
let mut i = 0;
while i < 4 {
y = y * (1.5 - 0.5 * nf * y * y);
i += 1;
}
let s0 = (nf * y) as u128;
let s2 = s0 * s0;
let mut s = if n >= s2 {
s0 + (((n - s2) as f64) * (0.5 * y)) as u128
} else {
s0.saturating_sub((((s2 - n) as f64) * (0.5 * y)) as u128)
};
while s > 0 && s * s > n {
s -= 1;
}
while (s + 1) * (s + 1) <= n {
s += 1;
}
s as u64
}
pub(crate) const fn isqrt_u128_any(n: u128) -> u64 {
if n >> 64 == 0 {
return (n as u64).isqrt();
}
let mut s = if n >> 127 == 0 {
isqrt_f64(n) as u128
} else {
(isqrt_f64(n >> 2) as u128) << 1
};
while s > 0 && s * s > n {
s -= 1;
}
while s < u64::MAX as u128 && (s + 1) * (s + 1) <= n {
s += 1;
}
s as u64
}
#[inline]
pub(crate) const fn isqrt_u128_with_rem(n: u128) -> (u64, bool, bool) {
let s = isqrt_u128_any(n);
let rem = n - (s as u128) * (s as u128);
(s, rem == 0, rem > s as u128)
}
pub(crate) fn isqrt_words<const N: usize, const R: usize, const R2: usize>(
n: &[u64; N],
) -> ([u64; R], bool, bool) {
const {
assert!(R2 == 2 * R && R2 >= N && N <= KNUTH_MAX_M && N >= 3);
}
let m = sig_limbs(n);
let mut root = [0u64; R];
if m <= 2 {
let v = (n[0] as u128) | ((n[1] as u128) << 64);
let (s, rem_zero, rem_gt_root) = isqrt_u128_with_rem(v);
root[0] = s;
return (root, rem_zero, rem_gt_root);
}
let bits = 64 * m - n[m - 1].leading_zeros() as usize;
let e = (bits - 128).next_multiple_of(2);
let (wo, bo) = (e / 64, e % 64);
let w = |i: usize| -> u128 { if i < N { n[i] as u128 } else { 0 } };
let t: u128 = if bo == 0 {
w(wo) | (w(wo + 1) << 64)
} else {
(w(wo) >> bo) | (w(wo + 1) << (64 - bo)) | (w(wo + 2) << (128 - bo))
};
let s0 = isqrt_u128_any(t);
let (hw, hb) = (e / 2 / 64, e / 2 % 64);
root[hw] = s0 << hb;
if hb != 0 {
root[hw + 1] = s0 >> (64 - hb);
}
let steps = if m <= 3 { 1 } else { 2 };
let mut i = 0;
while i < steps {
if sig_limbs(&root) < 2 {
root[0] = 0;
root[1] = 1;
}
let rl = sig_limbs(&root);
let mut q = [0u64; N];
let mut r = [0u64; R];
div_knuth(&mut q[..m - rl + 1], &mut r[..rl], &n[..m], &root[..rl]);
let mut carry: u128 = 0;
let mut sum = [0u64; N];
let mut j = 0;
while j < N {
let s = q[j] as u128 + if j < R { root[j] as u128 } else { 0 } + carry;
sum[j] = s as u64;
carry = s >> 64;
j += 1;
}
debug_assert!(carry == 0);
let mut j = 0;
while j < N {
let lo = sum[j] >> 1;
let hi = if j + 1 < N { sum[j + 1] << 63 } else { 0 };
sum[j] = lo | hi;
j += 1;
}
if !is_zero(&sum[R..]) {
root = [u64::MAX; R];
} else {
root.copy_from_slice(&sum[..R]);
}
i += 1;
}
let mut n_pad = [0u64; R2];
n_pad[..N].copy_from_slice(n);
let mut sq = [0u64; R2];
loop {
mul_words(&mut sq, &root, &root);
if cmp_words(&sq, &n_pad) == Ordering::Greater {
let mut j = 0;
while j < R {
let (v, borrow) = root[j].overflowing_sub(1);
root[j] = v;
if !borrow {
break;
}
j += 1;
}
} else {
break;
}
}
loop {
let mut r1 = root;
if mul_add_word(&mut r1, 1, 1) {
break; }
mul_words(&mut sq, &r1, &r1);
if cmp_words(&sq, &n_pad) == Ordering::Greater {
break;
}
root = r1;
}
mul_words(&mut sq, &root, &root);
let mut rem = [0u64; R2];
let mut borrow: u128 = 0;
let mut j = 0;
while j < R2 {
let d = n_pad[j] as u128;
let s = sq[j] as u128 + borrow;
if d >= s {
rem[j] = (d - s) as u64;
borrow = 0;
} else {
rem[j] = ((d + (1u128 << 64)) - s) as u64;
borrow = 1;
}
j += 1;
}
debug_assert!(borrow == 0);
let rem_zero = is_zero(&rem);
let mut root_pad = [0u64; R2];
root_pad[..R].copy_from_slice(&root);
let rem_gt_root = cmp_words(&rem, &root_pad) == Ordering::Greater;
(root, rem_zero, rem_gt_root)
}
pub fn isqrt_newton(n: u128) -> u64 {
if n == 0 {
return 0;
}
let bits = 128 - n.leading_zeros();
let shift = bits.div_ceil(2);
let div = |x: u128| {
let mut q = [n as u64, (n >> 64) as u64];
if x >> 64 != 0 {
return n >> 64;
}
div_words_by_word(&mut q, x as u64);
(q[0] as u128) | ((q[1] as u128) << 64)
};
let mut x0: u128 = 1 << shift;
let mut x1 = (x0 + div(x0)) >> 1;
while x1 < x0 {
x0 = x1;
x1 = (x0 + div(x0)) >> 1;
}
x0 as u64
}
pub(crate) const fn parse_decimal_mag_rounded<const W: usize>(
src: &str,
scale: u8,
mode: Rounding,
) -> Result<(bool, [u64; W]), AmountErrorKind> {
let bytes: &[u8] = src.as_bytes();
let scale = scale as i64;
debug_assert!(scale <= 19);
const CHUNK_DIGITS: u32 = 19;
let mut acc = [0u64; W];
let mut chunk: u64 = 0;
let mut chunk_len: u32 = 0;
let mut flushed = false;
if bytes.is_empty() {
return Err(AmountErrorKind::Empty);
}
let (sign, digits): (bool, &[u8]) = match bytes[0] {
b'+' => (false, bytes.split_at(1).1),
b'-' => (true, bytes.split_at(1).1),
_ => (false, bytes),
};
let n = digits.len();
let mut di = 0;
while di < n {
let d = digits[di].wrapping_sub(b'0');
if d > 9 {
break;
}
debug_assert!(chunk_len < CHUNK_DIGITS);
chunk = chunk.wrapping_mul(10).wrapping_add(d as u64);
chunk_len += 1;
di += 1;
if chunk_len == CHUNK_DIGITS {
if mul_add_word(&mut acc, upow10(CHUNK_DIGITS), chunk) {
return Err(AmountErrorKind::Overflow);
}
chunk = 0;
chunk_len = 0;
flushed = true;
}
}
let int_digits = di;
let mut nfrac: i64 = 0;
if di < n {
if digits[di] != b'.' {
return Err(AmountErrorKind::InvalidDigit);
}
di += 1;
while di < n && nfrac < scale {
let d = digits[di].wrapping_sub(b'0');
if d > 9 {
break;
}
debug_assert!(chunk_len < CHUNK_DIGITS);
chunk = chunk.wrapping_mul(10).wrapping_add(d as u64);
chunk_len += 1;
nfrac += 1;
di += 1;
if chunk_len == CHUNK_DIGITS {
if mul_add_word(&mut acc, upow10(CHUNK_DIGITS), chunk) {
return Err(AmountErrorKind::Overflow);
}
chunk = 0;
chunk_len = 0;
flushed = true;
}
}
}
if di == n {
if int_digits == 0 && nfrac == 0 {
return Err(AmountErrorKind::Empty);
}
if !flushed {
let wide = (chunk as u128) * (upow10((scale - nfrac) as u32) as u128);
let lo = wide as u64;
let hi = (wide >> 64) as u64;
if W == 1 && hi != 0 {
return Err(AmountErrorKind::Overflow);
}
let out: &mut [u64] = &mut acc;
out[0] = lo;
if W > 1 {
out[1] = hi;
}
return Ok((sign, acc));
}
}
parse_tail(
sign,
digits,
di,
scale,
mode,
acc,
chunk,
chunk_len,
nfrac + 1,
int_digits != 0 || nfrac != 0,
)
}
#[inline(never)]
#[allow(clippy::too_many_arguments)] const fn parse_tail<const W: usize>(
sign: bool,
digits: &[u8],
mut di: usize,
scale: i64,
mode: Rounding,
mut acc: [u64; W],
chunk: u64,
chunk_len: u32,
mut point: i64,
mut digit: bool,
) -> Result<(bool, [u64; W]), AmountErrorKind> {
let mut round_digit: u64 = 0; let mut sticky = false; while di < digits.len() {
let x = digits[di].wrapping_sub(b'0') as u64;
if x > 9 {
return Err(AmountErrorKind::InvalidDigit);
}
digit = true;
if point == scale + 1 {
round_digit = x;
} else if x != 0 {
sticky = true;
}
point += 1;
di += 1;
}
if !digit {
return Err(AmountErrorKind::Empty);
}
if chunk_len > 0 && mul_add_word(&mut acc, upow10(chunk_len), chunk) {
return Err(AmountErrorKind::Overflow);
}
if point <= scale && mul_add_word(&mut acc, upow10((scale + 1 - point) as u32), 0) {
return Err(AmountErrorKind::Overflow);
}
let round_up = match mode {
Rounding::HalfUp => round_digit >= 5,
Rounding::HalfDown => round_digit > 5 || (round_digit == 5 && sticky),
Rounding::HalfEven => round_digit > 5 || (round_digit == 5 && (sticky || acc[0] & 1 != 0)),
Rounding::Down => false,
Rounding::Up => round_digit != 0 || sticky,
};
if round_up && mul_add_word(&mut acc, 1, 1) {
return Err(AmountErrorKind::Overflow);
}
Ok((sign, acc))
}
const DIGIT_PAIRS: [u8; 200] = {
let mut t = [0u8; 200];
let mut i = 0;
while i < 100 {
t[2 * i] = b'0' + (i / 10) as u8;
t[2 * i + 1] = b'0' + (i % 10) as u8;
i += 1;
}
t
};
pub(crate) fn str_mag<'a>(
mag: &[u64],
neg: bool,
frac_digits: usize,
precision: Option<usize>,
sign: AmountSign,
buf: &'a mut [u8],
) -> Option<&'a str> {
debug_assert!(frac_digits <= 19 && mag.len() <= 4);
if precision.is_none() && buf.len() >= 24 && sig_limbs(mag) == 1 {
let mut pos = buf.len();
let mut v = mag[0];
let is_zero_val = v == 0;
let mut fd = frac_digits;
while fd > 0 && v.is_multiple_of(10) {
v /= 10;
fd -= 1;
}
if fd > 0 {
while fd >= 2 {
let p = (v % 100) as usize * 2;
v /= 100;
pos -= 2;
buf[pos] = DIGIT_PAIRS[p];
buf[pos + 1] = DIGIT_PAIRS[p + 1];
fd -= 2;
}
if fd > 0 {
pos -= 1;
buf[pos] = (v % 10) as u8 + b'0';
v /= 10;
}
pos -= 1;
buf[pos] = b'.';
}
while v >= 100 {
let p = (v % 100) as usize * 2;
v /= 100;
pos -= 2;
buf[pos] = DIGIT_PAIRS[p];
buf[pos + 1] = DIGIT_PAIRS[p + 1];
}
if v >= 10 {
let p = v as usize * 2;
pos -= 2;
buf[pos] = DIGIT_PAIRS[p];
buf[pos + 1] = DIGIT_PAIRS[p + 1];
} else {
pos -= 1;
buf[pos] = v as u8 + b'0';
}
match (sign, neg && !is_zero_val) {
(AmountSign::None, _) => {}
(_, true) => {
pos -= 1;
buf[pos] = b'-';
}
(AmountSign::Always, false) if !is_zero_val => {
pos -= 1;
buf[pos] = b'+';
}
_ => {}
}
return core::str::from_utf8(&buf[pos..]).ok();
}
let mut work = [0u64; 5];
work[..mag.len()].copy_from_slice(mag);
if let Some(precision) = precision {
if precision >= buf.len() {
return None;
}
if precision < frac_digits {
let round_scale = upow10((frac_digits - precision) as u32);
let dropped = div_words_by_word(&mut work, round_scale);
if dropped.wrapping_shl(1) >= round_scale {
mul_add_word(&mut work, 1, 1);
}
mul_add_word(&mut work, round_scale, 0);
}
}
let mut digits = [b'0'; 96];
let mut nd = 0usize;
while sig_limbs(&work) > 1 {
let mut rem = div_words_by_pow10::<19>(&mut work);
for _ in 0..9 {
let p = (rem % 100) as usize * 2;
rem /= 100;
digits[nd] = DIGIT_PAIRS[p + 1];
digits[nd + 1] = DIGIT_PAIRS[p];
nd += 2;
}
digits[nd] = rem as u8 + b'0';
nd += 1;
}
let mut rem = work[0];
while rem >= 100 {
let p = (rem % 100) as usize * 2;
rem /= 100;
digits[nd] = DIGIT_PAIRS[p + 1];
digits[nd + 1] = DIGIT_PAIRS[p];
nd += 2;
}
if rem >= 10 {
let p = rem as usize * 2;
digits[nd] = DIGIT_PAIRS[p + 1];
digits[nd + 1] = DIGIT_PAIRS[p];
nd += 2;
} else if rem != 0 {
digits[nd] = rem as u8 + b'0';
nd += 1;
}
if nd < frac_digits + 1 {
nd = frac_digits + 1;
}
let mut pos = buf.len();
macro_rules! push {
($c:expr) => {{
if pos == 0 {
return None;
}
pos -= 1;
buf[pos] = $c;
}};
}
let print_frac = match precision {
Some(p) => p,
None => {
let mut skip = 0;
while skip < frac_digits && digits[skip] == b'0' {
skip += 1;
}
frac_digits - skip
}
};
if print_frac > 0 {
for _ in frac_digits..print_frac {
push!(b'0');
}
let start = frac_digits - print_frac.min(frac_digits);
for digit in digits.iter().take(frac_digits).skip(start) {
push!(*digit);
}
push!(b'.');
}
for digit in digits.iter().take(nd).skip(frac_digits) {
push!(*digit);
}
let is_zero_val = is_zero(mag);
match (sign, neg && !is_zero_val) {
(AmountSign::None, _) => {}
(_, true) => push!(b'-'),
(AmountSign::Always, false) if !is_zero_val => push!(b'+'),
_ => {}
}
core::str::from_utf8(&buf[pos..]).ok()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::AmountSign;
const POW10_19: u64 = upow10(19);
struct Rng(u64);
impl Rng {
fn next(&mut self) -> u64 {
let mut x = self.0;
x ^= x >> 12;
x ^= x << 25;
x ^= x >> 27;
self.0 = x;
x.wrapping_mul(0x2545F4914F6CDD1D)
}
}
#[test]
fn test_div_2by1() {
let mut rng = Rng(0x12345678DEADBEEF);
let check = |hi: u64, lo: u64, d: u64| {
let n = ((hi as u128) << 64) | lo as u128;
let (q, r) = div_2by1(hi, lo, d);
assert_eq!(q as u128, n / d as u128, "q for {n} / {d}");
assert_eq!(r as u128, n % d as u128, "r for {n} / {d}");
};
check(0, 0, 1);
check(0, u64::MAX, 1);
check(0, u64::MAX, u64::MAX);
check(u64::MAX - 1, u64::MAX, u64::MAX);
check(1, 0, 2);
check(1, 1, 2);
check(0x7FFF_FFFF_FFFF_FFFF, u64::MAX, 1 << 63);
check(POW10_19 - 1, u64::MAX, POW10_19);
for _ in 0..20000 {
let d = rng.next() | 1;
let hi = rng.next() % d;
let lo = rng.next();
check(hi, lo, d);
let ds = (rng.next() % 1000) + 1;
check(rng.next() % ds, rng.next(), ds);
}
}
#[test]
fn test_div_words_by_pow10() {
fn check<const DIGITS: u8>(rng: &mut Rng) {
let d = upow10(DIGITS as u32);
for shape in 0..4 {
let mut u = [0u64; 4];
for x in u.iter_mut().take(shape + 1) {
*x = rng.next();
}
let mut a = u;
let ra = div_words_by_word(&mut a, d);
let mut b = u;
let rb = div_words_by_pow10::<DIGITS>(&mut b);
assert_eq!((a, ra), (b, rb), "DIGITS={DIGITS} u={u:?}");
let mut w = [0u64; 8];
w[..4].copy_from_slice(&u);
let mut wa = w;
let ra = div_words_by_word(&mut wa, d);
let mut wb = w;
let rb = div_words_by_pow10::<DIGITS>(&mut wb);
assert_eq!((wa, ra), (wb, rb), "DIGITS={DIGITS} w={w:?}");
}
}
let mut rng = Rng(0x9E3779B97F4A7C15);
for _ in 0..2000 {
check::<0>(&mut rng);
check::<1>(&mut rng);
check::<4>(&mut rng);
check::<8>(&mut rng);
check::<19>(&mut rng);
}
let mut z = [0u64; 4];
assert_eq!(div_words_by_pow10::<4>(&mut z), 0);
assert_eq!(z, [0u64; 4]);
}
#[test]
fn test_div_words_by_word() {
let mut u = [u64::MAX, u64::MAX, u64::MAX, u64::MAX];
let rem = div_words_by_word(&mut u, 10);
assert_eq!(rem, 5);
assert!(!mul_add_word(&mut u, 10, 5));
assert_eq!(u, [u64::MAX; 4]);
}
#[test]
fn test_mul_words() {
let mut c = [0u64; 4];
mul_words(&mut c, &[u64::MAX, u64::MAX], &[u64::MAX, u64::MAX]);
assert_eq!(c, [1, 0, u64::MAX - 1, u64::MAX]);
let mut rng = Rng(0xC0FFEE);
for _ in 0..5000 {
let a = ((rng.next() as u128) << 64) | rng.next() as u128;
let b = rng.next() as u128;
let mut c = [0u64; 3];
mul_words(&mut c, &[a as u64, (a >> 64) as u64], &[b as u64]);
let lo = a.wrapping_mul(b);
assert_eq!(c[0], lo as u64);
assert_eq!(c[1], (lo >> 64) as u64);
}
}
#[test]
fn test_div_knuth_reconstruct() {
let mut rng = Rng(0xFEEDFACE);
for iter in 0..20000 {
let n = 2 + (rng.next() as usize) % 3; let m = n + (rng.next() as usize) % (5 - n) + 1; let mut u = [0u64; 5];
let mut v = [0u64; 4];
for x in u.iter_mut().take(m) {
*x = rng.next();
}
for x in v.iter_mut().take(n) {
*x = rng.next();
}
if v[n - 1] == 0 {
v[n - 1] = 1 + (rng.next() >> 32);
}
if iter % 7 == 0 {
u[m - 1] = v[n - 1];
}
if iter % 11 == 0 {
v[n - 1] = 1 << 63;
}
let mut q = [0u64; 4];
let mut r = [0u64; 4];
div_knuth(&mut q[..m - n + 1], &mut r[..n], &u[..m], &v[..n]);
assert_eq!(cmp_words(&r[..n], &v[..n]), Ordering::Less);
let mut prod = [0u64; 9];
mul_words(&mut prod[..m + 1], &q[..m - n + 1], &v[..n]);
let mut carry: u128 = 0;
for i in 0..9 {
let add = if i < n { r[i] as u128 } else { 0 };
let s = prod[i] as u128 + add + carry;
prod[i] = s as u64;
carry = s >> 64;
}
assert_eq!(&prod[..m], &u[..m], "reconstruction failed");
assert!(is_zero(&prod[m..]));
}
}
#[derive(Default)]
struct DivKnuthProbe {
max_corrections: u32,
addback: bool,
eq_estimate: bool,
qhat_overflow: bool,
}
fn div_knuth_probed(q: &mut [u64], r: &mut [u64], u: &[u64], v: &[u64]) -> DivKnuthProbe {
let m = u.len();
let n = v.len();
let mut un = [0u64; KNUTH_MAX_M + 1];
let mut vn = [0u64; KNUTH_MAX_M];
let s = v[n - 1].leading_zeros();
if s == 0 {
vn[..n].copy_from_slice(v);
un[..m].copy_from_slice(u);
un[m] = 0;
} else {
for i in (1..n).rev() {
vn[i] = (v[i] << s) | (v[i - 1] >> (64 - s));
}
vn[0] = v[0] << s;
un[m] = u[m - 1] >> (64 - s);
for i in (1..m).rev() {
un[i] = (u[i] << s) | (u[i - 1] >> (64 - s));
}
un[0] = u[0] << s;
}
for x in q.iter_mut() {
*x = 0;
}
const B: u128 = 1u128 << 64;
let vtop = vn[n - 1];
let vnext = vn[n - 2];
let mut probe = DivKnuthProbe::default();
let mut j = m - n;
loop {
let (mut qhat, mut rhat): (u128, u128) = if un[j + n] == vtop {
probe.eq_estimate = true;
(
B + (un[j + n - 1] / vtop) as u128,
(un[j + n - 1] % vtop) as u128,
)
} else {
let (q0, r0) = div_2by1(un[j + n], un[j + n - 1], vtop);
(q0 as u128, r0 as u128)
};
let mut corrections = 0u32;
loop {
if qhat >= B || qhat * (vnext as u128) > ((rhat << 64) | un[j + n - 2] as u128) {
qhat -= 1;
corrections += 1;
rhat += vtop as u128;
if rhat < B {
continue;
}
}
break;
}
probe.max_corrections = probe.max_corrections.max(corrections);
if qhat >= B {
probe.qhat_overflow = true;
}
let mut qd = qhat as u64;
let mut borrow: i128 = 0;
for i in 0..n {
let p = (qd as u128) * (vn[i] as u128);
let t = (un[i + j] as i128) - borrow - ((p as u64) as i128);
un[i + j] = t as u64;
borrow = ((p >> 64) as i128) - (t >> 64);
}
let t = (un[j + n] as i128) - borrow;
un[j + n] = t as u64;
if t < 0 {
probe.addback = true;
qd -= 1;
let mut carry: u128 = 0;
for i in 0..n {
let sum = un[i + j] as u128 + vn[i] as u128 + carry;
un[i + j] = sum as u64;
carry = sum >> 64;
}
un[j + n] = un[j + n].wrapping_add(carry as u64);
}
q[j] = qd;
if j == 0 {
break;
}
j -= 1;
}
if s == 0 {
r[..n].copy_from_slice(&un[..n]);
} else {
for i in 0..n - 1 {
r[i] = (un[i] >> s) | (un[i + 1] << (64 - s));
}
r[n - 1] = un[n - 1] >> s;
}
for x in r.iter_mut().skip(n) {
*x = 0;
}
probe
}
fn check_div_knuth(u: &[u64], v: &[u64]) -> DivKnuthProbe {
let m = u.len();
let n = v.len();
let mut q = [0u64; KNUTH_MAX_M];
let mut r = [0u64; KNUTH_MAX_M];
div_knuth(&mut q[..m - n + 1], &mut r[..n], u, v);
let mut q2 = [0u64; KNUTH_MAX_M];
let mut r2 = [0u64; KNUTH_MAX_M];
let probe = div_knuth_probed(&mut q2[..m - n + 1], &mut r2[..n], u, v);
assert_eq!(q, q2, "probed copy diverged: u={u:?} v={v:?}");
assert_eq!(r, r2, "probed copy diverged: u={u:?} v={v:?}");
assert!(
!probe.qhat_overflow,
"qhat >= B survived the correction loop: u={u:?} v={v:?}"
);
assert_eq!(cmp_words(&r[..n], &v[..n]), Ordering::Less, "r >= v");
let mut prod = [0u64; 2 * KNUTH_MAX_M + 1];
mul_words(&mut prod[..m + 1], &q[..m - n + 1], &v[..n]);
let mut carry: u128 = 0;
for (i, p) in prod.iter_mut().enumerate() {
let add = if i < n { r[i] as u128 } else { 0 };
let s = *p as u128 + add + carry;
*p = s as u64;
carry = s >> 64;
}
assert_eq!(&prod[..m], u, "reconstruction failed: u={u:?} v={v:?}");
assert!(is_zero(&prod[m..]));
probe
}
#[test]
fn test_div_knuth_d3_adversarial() {
let iters: u64 = if cfg!(debug_assertions) {
200_000
} else {
2_000_000
};
let mut rng = Rng(0xA160B17D);
let specials: [u64; 8] = [
1 << 63,
(1 << 63) | 1,
(1 << 63) - 1, u64::MAX,
u64::MAX - 1,
(1 << 63) + (1 << 62),
1, (u64::MAX >> 1) + 1,
];
let mut max_corrections = 0u32;
let mut addback_seen = false;
let mut eq_estimate_seen = false;
let mut s_zero_seen = false;
let mut s_shift_seen = false;
let mut m_eq_n_seen = false;
for iter in 0..iters {
let n = 2 + (rng.next() as usize) % 3; let m = n + (rng.next() as usize) % (6 - n); let mut u = [0u64; 5];
let mut v = [0u64; 4];
for x in u.iter_mut().take(m) {
*x = match rng.next() % 4 {
0 => specials[(rng.next() as usize) % specials.len()],
1 => u64::MAX,
_ => rng.next(),
};
}
for x in v.iter_mut().take(n) {
*x = match rng.next() % 4 {
0 => specials[(rng.next() as usize) % specials.len()],
1 => u64::MAX,
_ => rng.next(),
};
}
if v[n - 1] == 0 {
v[n - 1] = specials[(rng.next() as usize) % specials.len()];
}
match iter % 5 {
0 => {
v[n - 1] = 1 << 63;
v[n - 2] = u64::MAX;
u[m - 1] = v[n - 1];
u[m - 2] = u64::MAX;
}
1 => u[m - 1] = v[n - 1],
2 => {
v[n - 1] = 1 << 63;
u[m - 1] = (1 << 63) - 1;
u[m - 2] = u64::MAX;
}
_ => {}
}
let probe = check_div_knuth(&u[..m], &v[..n]);
max_corrections = max_corrections.max(probe.max_corrections);
addback_seen |= probe.addback;
eq_estimate_seen |= probe.eq_estimate;
s_zero_seen |= v[n - 1] >= 1 << 63;
s_shift_seen |= v[n - 1] < 1 << 63;
m_eq_n_seen |= m == n;
}
assert_eq!(max_corrections, 2, "expected the 2-correction worst case");
assert!(addback_seen, "add-back path never exercised");
assert!(eq_estimate_seen, "qhat >= B estimate path never exercised");
assert!(s_zero_seen && s_shift_seen, "both normalization paths");
assert!(m_eq_n_seen, "m == n (single quotient limb) never exercised");
}
#[test]
fn test_isqrt_words_self_check() {
fn check<const N: usize, const R: usize, const R2: usize>(n: &[u64; N]) {
let (root, rem_zero, rem_gt) = isqrt_words::<N, R, R2>(n);
let mut n_pad = [0u64; R2];
n_pad[..N].copy_from_slice(n);
let mut sq = [0u64; R2];
mul_words(&mut sq, &root, &root);
assert_ne!(cmp_words(&sq, &n_pad), Ordering::Greater, "root^2 > n");
let mut r1 = root;
if !mul_add_word(&mut r1, 1, 1) {
mul_words(&mut sq, &r1, &r1);
assert_eq!(cmp_words(&sq, &n_pad), Ordering::Greater, "(root+1)^2 <= n");
}
mul_words(&mut sq, &root, &root);
let mut rem = [0u64; R2];
let mut borrow: i128 = 0;
for j in 0..R2 {
let d = n_pad[j] as i128 - sq[j] as i128 - borrow;
rem[j] = d as u64;
borrow = (d < 0) as i128;
}
assert_eq!(borrow, 0);
assert_eq!(rem_zero, is_zero(&rem), "rem_zero");
let mut rp = [0u64; R2];
rp[..R].copy_from_slice(&root);
assert_eq!(
rem_gt,
cmp_words(&rem, &rp) == Ordering::Greater,
"rem_gt_root"
);
}
check::<4, 2, 4>(&[u64::MAX; 4]);
check::<6, 3, 6>(&[u64::MAX; 6]);
check::<4, 2, 4>(&[0, 0, 0, u64::MAX]);
check::<6, 3, 6>(&[0, 0, 0, 0, 0, u64::MAX]);
let mut rng = Rng(0x15C4A57B00B5);
for iter in 0..20000 {
let mut n4 = [0u64; 4];
let mut n6 = [0u64; 6];
let w4 = 1 + (rng.next() as usize) % 3; let w6 = 1 + (rng.next() as usize) % 5; for x in n4.iter_mut().take(w4) {
*x = rng.next();
}
for x in n6.iter_mut().take(w6) {
*x = rng.next();
}
match iter % 5 {
0 => {
n4[w4 - 1] |= 1 << 63; n6[w6 - 1] |= 1 << 63;
}
1 => {
n4[w4 - 1] = 1; n6[w6 - 1] = 1;
}
2 => {
let s = rng.next() as u128;
let sq = s * s;
n4 = [sq as u64, (sq >> 64) as u64, 0, 0];
n6 = [sq as u64, (sq >> 64) as u64, 0, 0, 0, 0];
if iter % 8 == 6 {
n4[0] = n4[0].wrapping_add(1);
n6[0] = n6[0].wrapping_add(1);
}
}
3 => {
let r4 = [rng.next(), rng.next() >> 33, 0, 0]; let r6 = [rng.next(), rng.next(), rng.next() >> 34, 0, 0, 0]; let mut sq4 = [0u64; 4];
mul_words(&mut sq4, &r4[..2], &r4[..2]);
let mut sq6 = [0u64; 6];
mul_words(&mut sq6, &r6[..3], &r6[..3]);
n4 = sq4;
n6 = sq6;
match iter % 3 {
0 => {
let b4 = n4[0] == 0;
n4[0] = n4[0].wrapping_sub(1);
if b4 {
n4[1] = n4[1].wrapping_sub(1);
}
let b6 = n6[0] == 0;
n6[0] = n6[0].wrapping_sub(1);
if b6 {
n6[1] = n6[1].wrapping_sub(1);
}
}
1 => {
n4[0] = n4[0].wrapping_add(1);
n6[0] = n6[0].wrapping_add(1);
}
_ => {}
}
}
_ => {}
}
check::<4, 2, 4>(&n4);
check::<6, 3, 6>(&n6);
}
}
#[test]
fn test_div_knuth_hard_path_fixtures() {
let u = [
9223372036854775808,
8792676872254834457,
18446744073709551615,
9223372036854775808,
];
let v = [
14320622906001072715,
9223372036854775808,
18446744073709551615,
9223372036854775808,
];
assert!(check_div_knuth(&u, &v).addback, "add-back, s == 0");
let u = [
15833662907273503877,
1193572382016947486,
9223372036854775808,
18446744073709551615,
9223372036854775807,
];
let v = [
10296164541181878266,
10908281225356688296,
18446744073709551615,
9223372036854775807,
];
assert!(check_div_knuth(&u, &v).addback, "add-back, s > 0");
let u = [
10216183818892624758,
12442129881179520362,
18446744073709551615,
9223372036854775808,
];
let v = [
16550061096035433150,
18446744073709551615,
9223372036854775808,
];
assert_eq!(check_div_knuth(&u, &v).max_corrections, 2, "2 corrections");
let u = [
7873722608829012771,
1077289598778399907,
18446744073709551615,
];
let v = [18446744073709551615, 18446744073709551615];
assert!(check_div_knuth(&u, &v).eq_estimate, "qhat >= B estimate");
let u = [0x123456789ABCDEF0, u64::MAX, 0xFEDCBA9876543210];
let v = [u64::MAX, 1];
check_div_knuth(&u, &v);
let u = [7u64, 3];
let v = [9u64, u64::MAX];
let mut q = [0xAAAA_AAAA_AAAA_AAAAu64; 3];
let mut r = [0xAAAA_AAAA_AAAA_AAAAu64; 4];
div_knuth(&mut q, &mut r, &u, &v);
assert_eq!(q, [0, 0, 0], "quotient and excess limbs zeroed");
assert_eq!(r, [7, 3, 0, 0], "remainder and excess limbs zeroed");
}
#[test]
fn test_dec_div_rounding_edges() {
let b = 1u64 << 63;
let a2 = [0u64, 7];
let b2 = [0u64, 2];
let r = dec_div::<0, 2, 3>(false, &a2, false, &b2, Rounding::HalfEven);
assert_eq!(r, Some((false, [4, 0])), "tie rounds odd quotient up");
let r = dec_div::<0, 2, 3>(false, &a2, false, &b2, Rounding::Down);
assert_eq!(r, Some((false, [3, 0])), "tie truncates under Down");
let a4 = [3u64, 0, 2, 0]; let b4 = [1u64, b, 0, 0];
let r = dec_div::<0, 4, 5>(false, &a4, false, &b4, Rounding::HalfUp);
assert_eq!(r, Some((false, [4, 0, 0, 0])), "shl1 carry means rem > b/2");
let r = dec_div::<0, 4, 5>(false, &a4, false, &b4, Rounding::Down);
assert_eq!(r, Some((false, [3, 0, 0, 0])));
let a2 = [8116567392432202711u64, 1660206966633859645];
let b2 = [9u64, 0];
let r = dec_div::<2, 2, 3>(false, &a2, false, &b2, Rounding::HalfUp);
assert_eq!(r, None, "round-up past all-ones quotient overflows");
let r = dec_div::<2, 2, 3>(false, &a2, false, &b2, Rounding::Down);
assert_eq!(r, None);
let a4 = [u64::MAX, u64::MAX, 0, 0];
let b4 = [1u64, 0, 0, 0];
let r = dec_div::<0, 4, 5>(false, &a4, false, &b4, Rounding::Down);
assert_eq!(r, Some((false, [u64::MAX, u64::MAX, 0, 0])));
}
#[test]
fn test_dec_mul_overflow_edges() {
let a2 = [0u64, 1 << 62];
let b2 = [4u64, 0];
let r = dec_mul::<0, 2, 4>(false, &a2, false, &b2, Rounding::Down);
assert_eq!(r, None, "re-scaled product exceeds W limbs");
let b1 = [(u64::MAX as u128 * 2 + 1).div_euclid(31) as u64]; let r = dec_mul::<1, 1, 2>(false, &[155], false, &b1, Rounding::HalfEven);
assert_eq!(r, None, "round-up past all-ones product overflows");
let r = dec_mul::<1, 1, 2>(false, &[155], false, &b1, Rounding::Down);
assert_eq!(r, None);
let a4 = [u64::MAX, 0, 0, 0];
let r = dec_mul::<0, 4, 8>(false, &a4, false, &a4, Rounding::Down);
assert_eq!(r, Some((false, [1, u64::MAX - 1, 0, 0])), "(2^64-1)^2");
}
#[test]
fn test_div_rem_u128_pow10_edges() {
assert_eq!(div_rem_u128_pow10::<0>(12345), (12345, 0));
assert_eq!(div_rem_u128_pow10::<0>(u128::MAX), (u128::MAX, 0));
let n = (10_000_000u128 << 64) + 123_456_789;
assert_eq!(
div_rem_u128_pow10::<4>(n),
(n / 10_000, (n % 10_000) as u64)
);
assert_eq!(
div_rem_u128_pow10::<19>(u128::MAX),
(
u128::MAX / POW10_19 as u128,
(u128::MAX % POW10_19 as u128) as u64,
)
);
}
#[test]
fn test_div_knuth_exact_and_edges() {
let u = [0u64, 0, 0, 1];
let v = [0u64, 0, 1];
let mut q = [0u64; 2];
let mut r = [0u64; 3];
div_knuth(&mut q, &mut r, &u, &v);
assert_eq!(q, [0, 1]);
assert!(is_zero(&r));
let u = [5u64, 7, 9];
let v = [1u64, 1 << 63];
let mut q = [0u64; 2];
let mut r = [0u64; 2];
div_knuth(&mut q, &mut r, &u, &v);
let mut prod = [0u64; 4];
mul_words(&mut prod, &q, &v);
let mut carry: u128 = 0;
for i in 0..4 {
let add = if i < 2 { r[i] as u128 } else { 0 };
let s = prod[i] as u128 + add + carry;
prod[i] = s as u64;
carry = s >> 64;
}
assert_eq!(&prod[..3], &u);
assert_eq!(prod[3], 0);
}
#[test]
fn test_parse_mag() {
use crate::Rounding::*;
assert_eq!(
parse_decimal_mag_rounded::<4>("1.0001", 4, HalfUp),
Ok((false, [10001, 0, 0, 0]))
);
assert_eq!(
parse_decimal_mag_rounded::<4>("-1.00005", 4, HalfUp),
Ok((true, [10001, 0, 0, 0]))
);
assert_eq!(
parse_decimal_mag_rounded::<4>("1.00005", 4, HalfEven),
Ok((false, [10000, 0, 0, 0]))
);
assert_eq!(
parse_decimal_mag_rounded::<4>("", 4, HalfUp),
Err(AmountErrorKind::Empty)
);
assert_eq!(
parse_decimal_mag_rounded::<4>("1.2.3", 4, HalfUp),
Err(AmountErrorKind::InvalidDigit)
);
let (neg, mag) =
parse_decimal_mag_rounded::<4>("340282366920938463463374607431768211456", 0, HalfUp)
.unwrap();
assert!(!neg);
assert_eq!(mag, [0, 0, 1, 0]);
let huge = "9".repeat(80);
assert_eq!(
parse_decimal_mag_rounded::<4>(&huge, 0, HalfUp),
Err(AmountErrorKind::Overflow)
);
}
#[test]
fn test_parse_rounding_matrix() {
use crate::Rounding::*;
for k in *b"45" {
let kept = 120 + (k - b'0') as u64;
for r in 0..10u8 {
for s in 0..2u8 {
let rem = (r as u64) * 10 + s as u64;
let src = [b'1', b'.', b'2', k, b'0' + r, b'0' + s];
let src = core::str::from_utf8(&src).unwrap();
let want = |up: bool| Ok((false, [kept + up as u64]));
let got = |mode| parse_decimal_mag_rounded::<1>(src, 2, mode);
assert_eq!(got(Down), want(false), "{src}");
assert_eq!(got(Up), want(rem != 0), "{src}");
assert_eq!(got(HalfUp), want(2 * rem >= 100), "{src}");
assert_eq!(got(HalfDown), want(2 * rem > 100), "{src}");
let even = 2 * rem > 100 || (2 * rem == 100 && kept % 2 == 1);
assert_eq!(got(HalfEven), want(even), "{src}");
}
}
}
}
#[test]
fn test_parse_round_carry_overflow() {
use crate::Rounding::{Down, HalfUp};
assert_eq!(
parse_decimal_mag_rounded::<1>("18446744073709551615.5", 0, HalfUp),
Err(AmountErrorKind::Overflow)
);
assert_eq!(
parse_decimal_mag_rounded::<2>("18446744073709551615.5", 0, HalfUp),
Ok((false, [0, 1]))
);
assert_eq!(
parse_decimal_mag_rounded::<1>("18446744073709551615.5", 0, Down),
Ok((false, [u64::MAX]))
);
assert_eq!(
parse_decimal_mag_rounded::<1>("18446744073709551614.5", 0, HalfUp),
Ok((false, [u64::MAX]))
);
assert_eq!(
parse_decimal_mag_rounded::<2>("340282366920938463463374607431768211455.5", 0, HalfUp),
Err(AmountErrorKind::Overflow)
);
assert_eq!(
parse_decimal_mag_rounded::<4>("340282366920938463463374607431768211455.5", 0, HalfUp),
Ok((false, [0, 0, 1, 0]))
);
assert_eq!(
parse_decimal_mag_rounded::<4>(
"115792089237316195423570985008687907853269984665640564039457584007913129639935.5",
0,
HalfUp
),
Err(AmountErrorKind::Overflow)
);
}
#[test]
fn test_parse_no_integer_digits() {
use crate::Rounding::*;
let p = |s, scale, mode| parse_decimal_mag_rounded::<1>(s, scale, mode);
assert_eq!(p(".5", 0, HalfUp), Ok((false, [1])));
assert_eq!(p(".5", 0, HalfDown), Ok((false, [0])));
assert_eq!(p(".5", 0, HalfEven), Ok((false, [0])));
assert_eq!(p(".5", 0, Down), Ok((false, [0])));
assert_eq!(p(".5", 0, Up), Ok((false, [1])));
assert_eq!(p(".4", 0, HalfUp), Ok((false, [0])));
assert_eq!(p("-.5", 0, HalfUp), Ok((true, [1])));
assert_eq!(p(".00005", 4, HalfUp), Ok((false, [1])));
assert_eq!(p(".000049", 4, HalfUp), Ok((false, [0])));
assert_eq!(p(".00005", 4, HalfEven), Ok((false, [0])));
assert_eq!(p(".5", 4, HalfUp), Ok((false, [5000])));
assert_eq!(p("-.5", 4, HalfUp), Ok((true, [5000])));
assert_eq!(p(".", 4, HalfUp), Err(AmountErrorKind::Empty));
assert_eq!(p("-.", 4, HalfUp), Err(AmountErrorKind::Empty));
assert_eq!(p(".", 0, HalfUp), Err(AmountErrorKind::Empty));
assert_eq!(p(".x", 0, HalfUp), Err(AmountErrorKind::InvalidDigit));
assert_eq!(p(".x", 4, HalfUp), Err(AmountErrorKind::InvalidDigit));
assert_eq!(p(".5x", 0, HalfUp), Err(AmountErrorKind::InvalidDigit));
assert_eq!(p("..5", 4, HalfUp), Err(AmountErrorKind::InvalidDigit));
assert_eq!(p(".5.5", 4, HalfUp), Err(AmountErrorKind::InvalidDigit));
}
#[test]
fn test_parse_chunk_boundary() {
use crate::Rounding::HalfUp;
assert_eq!(
parse_decimal_mag_rounded::<1>("123456789012345.6789", 4, HalfUp),
Ok((false, [1234567890123456789]))
);
assert_eq!(
parse_decimal_mag_rounded::<1>("123456789012345.6789x", 4, HalfUp),
Err(AmountErrorKind::InvalidDigit)
);
assert_eq!(
parse_decimal_mag_rounded::<1>("123456789012345.67891", 4, HalfUp),
Ok((false, [1234567890123456789]))
);
assert_eq!(
parse_decimal_mag_rounded::<1>("123456789012345.67895", 4, HalfUp),
Ok((false, [1234567890123456790]))
);
assert_eq!(
parse_decimal_mag_rounded::<1>("1234567890123456.789", 4, HalfUp),
Ok((false, [12345678901234567890]))
);
assert_eq!(
parse_decimal_mag_rounded::<2>("12345678901234567.89", 4, HalfUp),
Ok((false, [12776324570088369204, 6]))
);
assert_eq!(
parse_decimal_mag_rounded::<1>("999999999999999999", 0, HalfUp),
Ok((false, [999999999999999999]))
);
assert_eq!(
parse_decimal_mag_rounded::<1>("9999999999999999999", 0, HalfUp),
Ok((false, [9999999999999999999]))
);
assert_eq!(
parse_decimal_mag_rounded::<1>("00000000000000000000000001", 0, HalfUp),
Ok((false, [1]))
);
assert_eq!(
parse_decimal_mag_rounded::<1>("0.00000000000000000000000001", 4, HalfUp),
Ok((false, [0]))
);
}
#[test]
fn test_parse_inline_finish_limits() {
use crate::Rounding::HalfUp;
assert_eq!(
parse_decimal_mag_rounded::<1>("1844674407370955.16", 4, HalfUp),
Ok((false, [18446744073709551600]))
);
assert_eq!(
parse_decimal_mag_rounded::<1>("1844674407370955.17", 4, HalfUp),
Err(AmountErrorKind::Overflow)
);
assert_eq!(
parse_decimal_mag_rounded::<2>("1844674407370955.17", 4, HalfUp),
Ok((false, [84, 1]))
);
assert_eq!(
parse_decimal_mag_rounded::<1>("999999999999999999", 19, HalfUp),
Err(AmountErrorKind::Overflow)
);
assert_eq!(
parse_decimal_mag_rounded::<2>("999999999999999999", 19, HalfUp),
Ok((false, [8515484028849618944, 542101086242752216]))
);
assert_eq!(
parse_decimal_mag_rounded::<1>("0", 0, HalfUp),
Ok((false, [0]))
);
assert_eq!(
parse_decimal_mag_rounded::<1>("0", 19, HalfUp),
Ok((false, [0]))
);
assert_eq!(
parse_decimal_mag_rounded::<1>("-0.0", 4, HalfUp),
Ok((true, [0]))
);
assert_eq!(
parse_decimal_mag_rounded::<1>("7.", 4, HalfUp),
Ok((false, [70000]))
);
}
#[test]
fn test_parse_random_vs_exact_reference() {
use crate::Rounding::*;
let modes = [HalfUp, HalfDown, HalfEven, Down, Up];
let mut rng = Rng(0x0BADC0DE5EED1234);
for _ in 0..20000 {
let m = 1 + (rng.next() % 18) as usize;
let ip = (rng.next() % (m as u64 + 1)) as usize;
let scale = (rng.next() % 20) as u8;
let neg = rng.next() & 1 != 0;
let mode = modes[(rng.next() % 5) as usize];
let mut buf = [0u8; 24];
let mut len = 0;
if neg {
buf[len] = b'-';
len += 1;
}
let mut d: u128 = 0;
for i in 0..m {
if i == ip {
buf[len] = b'.';
len += 1;
}
let digit = (rng.next() % 10) as u8;
d = d * 10 + digit as u128;
buf[len] = b'0' + digit;
len += 1;
}
let src = core::str::from_utf8(&buf[..len]).unwrap();
let frac = (m - ip) as u32;
let (mag, rem, div) = if frac <= scale as u32 {
(d * upow10(scale as u32 - frac) as u128, 0u128, 1u128)
} else {
let div = upow10(frac - scale as u32) as u128;
(d / div, d % div, div)
};
let up = match mode {
Down => false,
Up => rem != 0,
HalfUp => 2 * rem >= div,
HalfDown => 2 * rem > div,
HalfEven => 2 * rem > div || (2 * rem == div && mag & 1 == 1),
};
let want = mag + up as u128;
assert_eq!(
parse_decimal_mag_rounded::<2>(src, scale, mode),
Ok((neg, [want as u64, (want >> 64) as u64])),
"{src} at scale {scale} with {mode:?}"
);
}
}
#[test]
fn test_str_mag() {
extern crate std;
let mut buf = [0u8; 128];
let m = |v: u64| [v, 0, 0, 0];
assert_eq!(
str_mag(&m(10000), false, 4, None, AmountSign::Negative, &mut buf),
Some("1")
);
assert_eq!(
str_mag(&m(10001), false, 4, None, AmountSign::Negative, &mut buf),
Some("1.0001")
);
assert_eq!(
str_mag(&m(10001), true, 4, None, AmountSign::Negative, &mut buf),
Some("-1.0001")
);
assert_eq!(
str_mag(&m(10050), false, 4, Some(2), AmountSign::Negative, &mut buf),
Some("1.01")
);
assert_eq!(
str_mag(&m(10000), false, 4, Some(5), AmountSign::Negative, &mut buf),
Some("1.00000")
);
assert_eq!(
str_mag(&m(10000), false, 4, None, AmountSign::Always, &mut buf),
Some("+1")
);
assert_eq!(
str_mag(&m(0), false, 4, None, AmountSign::Always, &mut buf),
Some("0")
);
assert_eq!(
str_mag(
&[0, 0, 1, 0],
false,
4,
None,
AmountSign::Negative,
&mut buf
),
Some("34028236692093846346337460743176821.1456")
);
assert_eq!(
str_mag(&m(u64::MAX), false, 4, None, AmountSign::Negative, &mut buf),
Some("1844674407370955.1615")
);
assert_eq!(
str_mag(
&[0, 1, 0, 0],
false,
0,
None,
AmountSign::Negative,
&mut buf
),
Some("18446744073709551616")
);
assert_eq!(
str_mag(
&[7766279631452241920, 5, 0, 0],
false,
0,
None,
AmountSign::Negative,
&mut buf
),
Some("100000000000000000000")
);
let s = "-1234567890123456789012345678901234567890123456789012345.67891";
let (neg, mag) = parse_decimal_mag_rounded::<4>(s, 5, crate::Rounding::HalfUp).unwrap();
assert_eq!(
str_mag(&mag, neg, 5, None, AmountSign::Negative, &mut buf),
Some(s)
);
let mut small = [0u8; 8];
assert_eq!(
str_mag(&m(10001), true, 4, None, AmountSign::Negative, &mut small),
Some("-1.0001")
);
assert_eq!(
str_mag(
&m(u64::MAX),
false,
4,
None,
AmountSign::Negative,
&mut small
),
None
);
}
#[test]
fn test_isqrt_cores() {
let check = |n: u128| {
let want = n.isqrt() as u64;
assert_eq!(isqrt_f64(n), want, "isqrt_f64({n})");
assert_eq!(isqrt_newton(n), want, "isqrt_newton({n})");
};
for n in 0..2000u128 {
check(n);
}
for bit in 1..63u32 {
let k = (1u128 << bit) - 1;
for s in [k * k, k * k + 1, k * k + 2 * k] {
check(s);
}
}
let top = i64::MAX as u128 * POW10_19 as u128;
for n in [top, top - 1, 1 << 126, (1 << 126) + 1, (1 << 127) - 1] {
check(n);
}
let mut rng = Rng(0x5851F42D4C957F2D);
for i in 0..20000u32 {
let bits = 1 + (i % 127);
let top = 1u128 << (bits - 1);
let n = top | (((rng.next() as u128) << 64 | rng.next() as u128) & (top - 1));
check(n);
}
}
}