use crate::core_type::I128;
const MG_EXP_MAGICS: [(u128, u32); 39] = [
(0, 0),
(0x99999999999999999999999999999999, 124),
(0x47ae147ae147ae147ae147ae147ae147, 121),
(0x0624dd2f1a9fbe76c8b4395810624dd2, 118),
(0xa36e2eb1c432ca57a786c226809d4951, 114),
(0x4f8b588e368f08461f9f01b866e43aa7, 111),
(0x0c6f7a0b5ed8d36b4c7f34938583621f, 108),
(0xad7f29abcaf485787a6520ec08d23699, 104),
(0x5798ee2308c39df9fb841a566d74f87a, 101),
(0x12e0be826d694b2e62d01511f12a6061, 98),
(0xb7cdfd9d7bdbab7d6ae6881cb5109a36, 94),
(0x5fd7fe17964955fdef1ed34a2a73ae91, 91),
(0x19799812dea11197f27f0f6e885c8ba7, 88),
(0xc25c268497681c2650cb4be40d60df73, 84),
(0x6849b86a12b9b01ea70909833de71928, 81),
(0x203af9ee756159b21f3a6e0297ec1420, 78),
(0xcd2b297d889bc2b6985d7cd0f3135367, 74),
(0x70ef54646d496892137dfd73f5a90f85, 71),
(0x2725dd1d243aba0e75fe645cc4873f9e, 68),
(0xd83c94fb6d2ac34a5663d3c7a0d865ca, 64),
(0x79ca10c9242235d511e976394d79eb08, 61),
(0x2e3b40a0e9b4f7dda7edf82dd794bc06, 58),
(0xe392010175ee5962a6498d1625bac670, 54),
(0x82db34012b25144eeb6e0a781e2f0527, 51),
(0x357c299a88ea76a58924d52ce4f26a85, 48),
(0xef2d0f5da7dd8aa27507bb7b07ea4409, 44),
(0x8c240c4aecb13bb52a6c95fc0655033a, 41),
(0x3ce9a36f23c0fc90eebd44c99eaa68fb, 38),
(0xfb0f6be50601941b17953adc3110a7f8, 34),
(0x95a5efea6b34767c12ddc8b027408660, 31),
(0x4484bfeebc29f863424b06f3529a051a, 28),
(0x039d66589687f9e901d59f290ee19dae, 25),
(0x9f623d5a8a732974cfbc31db4b0295e4, 21),
(0x4c4e977ba1f5bac3d9635b15d59bab1c, 18),
(0x09d8792fb4c495697ab5e277de16227d, 15),
(0xa95a5b7f87a0ef0f2abc9d8c9689d0c8, 11),
(0x54484932d2e725a5bbca17a3aba173d3, 8),
(0x1039d428a8b8eaeafca1ac82efb45ca9, 5),
(0xb38fb9daa78e44ab2dcf7a6b19209442, 1),
];
#[inline]
const fn mul2(a: u128, b: u128) -> (u128, u128) {
let (ahigh, alow) = (a >> 64, a & u64::MAX as u128);
let (bhigh, blow) = (b >> 64, b & u64::MAX as u128);
let (mid, carry1) = (alow * bhigh).overflowing_add(ahigh * blow);
let (mlow, carry2) = (alow * blow).overflowing_add(mid << 64);
let mhigh = ahigh * bhigh + (mid >> 64) + ((carry1 as u128) << 64) + carry2 as u128;
(mhigh, mlow)
}
#[inline]
fn div_exp_fast_2word(n_high: u128, n_low: u128, exp: u128, scale_idx: usize) -> Option<u128> {
if n_high >= exp {
return None;
}
let (magic, zeros) = MG_EXP_MAGICS[scale_idx];
let z_high = (n_high << zeros) | (n_low >> (128 - zeros));
let z_low = n_low << zeros;
let (m1_high, _) = mul2(z_low, magic);
let (m2_high, m2_low) = mul2(z_high, magic);
let (m_low, carry) = m2_low.overflowing_add(m1_high);
let m_high = m2_high + carry as u128;
let (_, carry) = m_low.overflowing_add(z_low);
let q = m_high + z_high + carry as u128;
let (pp_high, pp_low) = mul2(q, exp);
let (r_low, borrow) = n_low.overflowing_sub(pp_low);
debug_assert!(n_high == pp_high + borrow as u128);
if r_low < exp {
Some(q)
} else {
Some(q + 1)
}
}
#[inline]
fn div_long_256_by_128(mut n_high: u128, mut n_low: u128, d: u128) -> Option<u128> {
if d == 0 {
return None;
}
if n_high == 0 {
return Some(n_low / d);
}
if n_high >= d {
return None;
}
let mut q: u128 = 0;
let mut rem: u128 = 0;
let mut i = 0;
while i < 256 {
rem = (rem << 1) | (n_high >> 127);
n_high = (n_high << 1) | (n_low >> 127);
n_low <<= 1;
q <<= 1;
if rem >= d {
rem -= d;
q |= 1;
}
i += 1;
}
Some(q)
}
#[inline]
pub(crate) fn mul_div_pow10<const SCALE: u32>(a: i128, b: i128) -> Option<i128> {
if SCALE == 0 {
return a.checked_mul(b);
}
if let Some(prod) = a.checked_mul(b) {
return Some(prod / I128::<SCALE>::multiplier());
}
let ua = a.unsigned_abs();
let ub = b.unsigned_abs();
let (mhigh, mlow) = mul2(ua, ub);
let exp = I128::<SCALE>::multiplier() as u128;
let q = div_exp_fast_2word(mhigh, mlow, exp, SCALE as usize)?;
let neg = (a < 0) ^ (b < 0);
if neg {
if q <= i128::MAX as u128 {
Some(-(q as i128))
} else if q == (i128::MAX as u128) + 1 {
Some(i128::MIN)
} else {
None
}
} else {
if q <= i128::MAX as u128 {
Some(q as i128)
} else {
None
}
}
}
#[inline]
pub(crate) fn div_pow10_div<const SCALE: u32>(a: i128, b: i128) -> Option<i128> {
if b == 0 {
return None;
}
if SCALE == 0 {
return a.checked_div(b);
}
let mult = I128::<SCALE>::multiplier();
if let Some(num) = a.checked_mul(mult) {
return Some(num / b);
}
let ua = a.unsigned_abs();
let umult = mult as u128;
let (mhigh, mlow) = mul2(ua, umult);
let ub = b.unsigned_abs();
let q = div_long_256_by_128(mhigh, mlow, ub)?;
let neg = (a < 0) ^ (b < 0);
if neg {
if q <= i128::MAX as u128 {
Some(-(q as i128))
} else if q == (i128::MAX as u128) + 1 {
Some(i128::MIN)
} else {
None
}
} else if q <= i128::MAX as u128 {
Some(q as i128)
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mul_div_pow10_small_matches_naive() {
const SCALE: u32 = 12;
let a: i128 = 1_500_000_000;
let b: i128 = 2_300_000_000;
let expected = (a * b) / 1_000_000_000_000_i128;
assert_eq!(mul_div_pow10::<SCALE>(a, b), Some(expected));
}
#[test]
fn mul_div_pow10_mid_matches_naive() {
const SCALE: u32 = 12;
let a: i128 = 3_000_000_000_000_000;
let b: i128 = 4_700_000_000_000_000;
let expected = (a * b) / 1_000_000_000_000_i128;
assert_eq!(mul_div_pow10::<SCALE>(a, b), Some(expected));
}
#[test]
fn mul_div_pow10_bound_matches_naive() {
const SCALE: u32 = 12;
let a: i128 = 7_000_000_000_000_000_000;
let b: i128 = 4_700_000_000_000_000_000;
let expected = (a * b) / 1_000_000_000_000_i128;
assert_eq!(mul_div_pow10::<SCALE>(a, b), Some(expected));
}
#[test]
fn mul_div_pow10_wide_correctness() {
const SCALE: u32 = 12;
let a: i128 = 50_000_000_000_000_000_000_000;
let b: i128 = 30_000_000_000_000_000_000_000;
let expected: i128 = 1_500_000_000_000_000_000_000_000_000_000_000_i128;
assert_eq!(mul_div_pow10::<SCALE>(a, b), Some(expected));
}
#[test]
fn mul_div_pow10_overflows_to_none() {
const SCALE: u32 = 12;
let a: i128 = 10_i128.pow(26);
let b: i128 = 10_i128.pow(26);
assert_eq!(mul_div_pow10::<SCALE>(a, b), None);
}
#[test]
fn mul_div_pow10_negative_one_sided() {
const SCALE: u32 = 12;
let a: i128 = -50_000_000_000_000_000_000_000;
let b: i128 = 30_000_000_000_000_000_000_000;
let expected: i128 = -1_500_000_000_000_000_000_000_000_000_000_000_i128;
assert_eq!(mul_div_pow10::<SCALE>(a, b), Some(expected));
}
#[test]
fn mul_div_pow10_negative_both() {
const SCALE: u32 = 12;
let a: i128 = -50_000_000_000_000_000_000_000;
let b: i128 = -30_000_000_000_000_000_000_000;
let expected: i128 = 1_500_000_000_000_000_000_000_000_000_000_000_i128;
assert_eq!(mul_div_pow10::<SCALE>(a, b), Some(expected));
}
#[test]
fn mul_div_pow10_scale_zero() {
const SCALE: u32 = 0;
assert_eq!(mul_div_pow10::<SCALE>(7, 11), Some(77));
assert_eq!(mul_div_pow10::<SCALE>(-7, 11), Some(-77));
assert_eq!(mul_div_pow10::<SCALE>(i128::MAX, 1), Some(i128::MAX));
assert_eq!(mul_div_pow10::<SCALE>(i128::MAX, 2), None); }
#[test]
fn mul_div_pow10_scale_one() {
const SCALE: u32 = 1;
assert_eq!(mul_div_pow10::<SCALE>(25, 4), Some(10));
}
#[test]
fn mul_div_pow10_scale_eighteen() {
const SCALE: u32 = 18;
let a: i128 = 10_i128.pow(18);
let b: i128 = 10_i128.pow(18);
assert_eq!(mul_div_pow10::<SCALE>(a, b), Some(10_i128.pow(18)));
}
#[test]
fn div_pow10_div_small_matches_naive() {
const SCALE: u32 = 12;
let a: i128 = 1_500_000_000;
let b: i128 = 7;
let expected = (a * 1_000_000_000_000_i128) / b;
assert_eq!(div_pow10_div::<SCALE>(a, b), Some(expected));
}
#[test]
fn div_pow10_div_by_zero_is_none() {
const SCALE: u32 = 12;
assert_eq!(div_pow10_div::<SCALE>(123, 0), None);
}
#[test]
fn div_pow10_div_scale_zero() {
const SCALE: u32 = 0;
assert_eq!(div_pow10_div::<SCALE>(15, 4), Some(3));
assert_eq!(div_pow10_div::<SCALE>(-15, 4), Some(-3));
assert_eq!(div_pow10_div::<SCALE>(i128::MIN, -1), None);
}
#[test]
fn div_pow10_div_wide_correctness() {
const SCALE: u32 = 12;
let a: i128 = 10_i128.pow(22);
let b: i128 = 2;
let expected: i128 = 5 * 10_i128.pow(33);
assert_eq!(div_pow10_div::<SCALE>(a, b), Some(expected));
}
#[test]
fn div_pow10_div_round_trip_small() {
const SCALE: u32 = 12;
let a: i128 = 6_000_000_000_000;
let b: i128 = 2_000_000_000_000;
assert_eq!(div_pow10_div::<SCALE>(a, b), Some(3_000_000_000_000));
}
#[test]
fn div_pow10_div_negative_dividend() {
const SCALE: u32 = 12;
let a: i128 = -6_000_000_000_000;
let b: i128 = 2_000_000_000_000;
assert_eq!(div_pow10_div::<SCALE>(a, b), Some(-3_000_000_000_000));
}
#[test]
fn mul_div_round_trip_wide() {
const SCALE: u32 = 12;
let a: i128 = 50_000_000_000_000_000_000_000;
let b: i128 = 30_000_000_000_000_000_000_000;
let prod = mul_div_pow10::<SCALE>(a, b).expect("wide mul");
let recovered = div_pow10_div::<SCALE>(prod, b).expect("wide div");
assert_eq!(recovered, a);
}
}