use super::core::{MAX_RAW, ONE_U};
const ONE_64: u64 = 1_000_000_000_000_000_000;
const CHUNK: u64 = 1_000_000_000;
const INV_5_9: u128 = 200_825_005_271_070_938_688_767_579_709_359_692_909;
const LIMIT_1E9: u128 = 340_282_366_920_938_463_463_374_607_431;
#[inline(always)]
pub(crate) fn div_exact_1e9(n: u128) -> Option<u128> {
let rotated = n.wrapping_mul(INV_5_9).rotate_right(9);
match rotated <= LIMIT_1E9 {
true => Some(rotated),
false => None,
}
}
#[inline(always)]
pub(crate) fn mul_raw(a: i128, b: i128) -> Option<i128> {
let negative = (a < 0) != (b < 0);
let (a, b) = (a.unsigned_abs(), b.unsigned_abs());
match (div_exact_1e9(a), div_exact_1e9(b)) {
(Some(left), Some(right)) => {
let magnitude = match (u64::try_from(left), u64::try_from(right)) {
(Ok(left), Ok(right)) => left as u128 * right as u128,
_ => left.checked_mul(right)?,
};
signed(negative, magnitude)
}
_ => mul_wide(a, b, negative),
}
}
fn mul_wide(a: u128, b: u128, negative: bool) -> Option<i128> {
if (a | b) > MAX_RAW as u128 {
return None;
}
let (a_int, a_frac) = (a / ONE_U, (a % ONE_U) as u64);
let (b_int, b_frac) = (b / ONE_U, (b % ONE_U) as u64);
let (tail, remainder) = mul_frac(a_frac, b_frac);
let magnitude = a
.checked_mul(b_int)?
.checked_add(a_int.checked_mul(b_frac as u128)?)?
.checked_add(tail as u128)?;
let rounded = match remainder * 2 >= ONE_64 {
true => magnitude.checked_add(1)?,
false => magnitude,
};
signed(negative, rounded)
}
fn mul_frac(a: u64, b: u64) -> (u64, u64) {
let (a_high, a_low) = (a / CHUNK, a % CHUNK);
let (b_high, b_low) = (b / CHUNK, b % CHUNK);
let high = a_high * b_high;
let middle = a_high * b_low + a_low * b_high;
let tail = (middle % CHUNK) * CHUNK + a_low * b_low;
(high + middle / CHUNK + tail / ONE_64, tail % ONE_64)
}
#[inline(always)]
fn signed(negative: bool, magnitude: u128) -> Option<i128> {
if magnitude > MAX_RAW as u128 {
return None;
}
Some(match negative {
true => -(magnitude as i128),
false => magnitude as i128,
})
}
#[cfg(test)]
mod multiply {
#![allow(clippy::unwrap_used)]
use super::*;
use crate::Dec;
use crate::dec;
use std::str::FromStr;
#[test]
fn test_the_magic_constants_match_their_definitions() {
let five_pow_nine: u128 = 5_u128.pow(9);
assert_eq!(INV_5_9.wrapping_mul(five_pow_nine), 1);
assert_eq!(LIMIT_1E9, u128::MAX / 1_000_000_000);
}
#[test]
fn test_exact_division_matches_plain_division() {
for value in [
0_u128,
1_000_000_000,
123_456_789_000_000_000,
u128::MAX / 2,
] {
let expected = match value.is_multiple_of(1_000_000_000) {
true => Some(value / 1_000_000_000),
false => None,
};
assert_eq!(div_exact_1e9(value), expected, "{value}");
}
assert_eq!(div_exact_1e9(7), None);
assert_eq!(div_exact_1e9(999_999_999), None);
}
#[test]
fn test_market_precision_takes_the_fast_path() {
assert!(div_exact_1e9(dec!(104_237.25).into_raw().unsigned_abs()).is_some());
assert!(div_exact_1e9(dec!(0.00135).into_raw().unsigned_abs()).is_some());
assert_eq!(dec!(104_237.25) * dec!(0.00135), dec!(140.72028750));
}
#[test]
fn test_finer_than_nine_decimals_takes_the_wide_path() {
let fine = Dec::from_str("0.0000000001").unwrap();
assert!(div_exact_1e9(fine.into_raw().unsigned_abs()).is_none());
assert_eq!(fine * dec!(3), Dec::from_str("0.0000000003").unwrap());
assert_eq!(Dec::EPSILON * dec!(1000), Dec::from_raw(1_000));
}
#[test]
fn test_both_paths_agree() {
for (left, right) in [
("1", "1"),
("2.5", "4"),
("-3.75", "8"),
("0.1", "0.1"),
("104237.25", "0.00135"),
("10000000000", "10000000000"),
("0.000000001", "0.000000001"),
] {
let a = Dec::from_str(left).unwrap();
let b = Dec::from_str(right).unwrap();
let wide = mul_wide(
a.into_raw().unsigned_abs(),
b.into_raw().unsigned_abs(),
(a.into_raw() < 0) != (b.into_raw() < 0),
);
assert_eq!(Some((a * b).into_raw()), wide, "{left} * {right}");
}
}
#[test]
fn test_signs_identities_and_overflow() {
assert_eq!(dec!(-2) * dec!(3), dec!(-6));
assert_eq!(dec!(-2) * dec!(-3), dec!(6));
assert_eq!(dec!(7.5) * Dec::ONE, dec!(7.5));
assert_eq!(dec!(7.5) * Dec::ZERO, Dec::ZERO);
assert_eq!(dec!(7.5) * Dec::NEG_ONE, dec!(-7.5));
assert_eq!(Dec::MAX.checked_mul(Dec::MAX), None);
assert_eq!(Dec::MAX.checked_mul(dec!(-2)), None);
assert_eq!(Dec::MAX.saturating_mul(dec!(-2)), Dec::MIN);
let mut value = dec!(3);
value *= dec!(4);
assert_eq!(value, dec!(12));
}
}