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 n = sig_limbs(b_mag);
let mut q = [0u64; WP1];
let (rem_cmp, rem_zero) = if n == 1 {
let d = b_mag[0];
let rem = div_words_by_word(&mut num, d);
q = num;
(cmp_twice_rem_u64(rem, d), rem == 0)
} else {
let mut r = [0u64; W];
let m = sig_limbs(&num);
if m < n {
r[..m].copy_from_slice(&num[..m]);
} else {
div_knuth(&mut q[..m - n + 1], &mut r[..n], &num[..m], &b_mag[..n]);
}
let mut r2 = [0u64; W];
r2[..n].copy_from_slice(&r[..n]);
let carry = shl1(&mut r2[..n]);
let cmp = if carry != 0 {
Ordering::Greater
} else {
cmp_words(&r2[..n], &b_mag[..n])
};
(cmp, is_zero(&r[..n]))
};
if q[W] != 0 {
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) 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 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 src: &[u8] = src.as_bytes();
let scale = scale as i64;
debug_assert!(scale <= 19);
let mut acc = [0u64; W];
let mut chunk: u64 = 0;
let mut chunk_len: u32 = 0;
let mut point: i64 = 0; let mut digit = false; let mut round_digit: u64 = 0; let mut sticky = false;
if src.is_empty() {
return Err(AmountErrorKind::Empty);
}
let (sign, digits): (bool, &[u8]) = match src[0] {
b'+' => (false, src.split_at(1).1),
b'-' => (true, src.split_at(1).1),
_ => (false, src),
};
let mut di = 0;
while di < digits.len() {
let s = digits[di];
di += 1;
match s {
b'.' if point > 0 => {
return Err(AmountErrorKind::InvalidDigit);
}
b'.' => point = 1,
c @ b'0'..=b'9' => {
digit = true;
let x = (c - b'0') as u64;
if point <= scale {
chunk = chunk * 10 + x;
chunk_len += 1;
if chunk_len == 19 {
if mul_add_word(&mut acc, upow10(19), chunk) {
return Err(AmountErrorKind::Overflow);
}
chunk = 0;
chunk_len = 0;
}
} else if point == scale + 1 {
round_digit = x;
} else if x != 0 {
sticky = true;
}
if point > 0 {
point += 1; }
}
_ => return Err(AmountErrorKind::InvalidDigit),
}
}
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 == 0 {
point = 1; }
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 % 10 == 0 {
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..]));
}
}
#[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_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);
}
}
}