#[allow(unused_imports)]
#[allow(unused_imports)]
use super::word::DecimalWord;
#[allow(unused_imports)]
use crate::fixed_point::width::WideInt;
#[allow(unused_imports)]
use crate::fixed_point::i256::I256;
#[allow(unused_imports)]
use crate::fixed_point::i512::I512;
#[allow(unused_imports)]
use crate::fixed_point::I1024;
use crate::fixed_point::domains::symbolic::rational::rational_number::OverflowDetected;
#[inline]
pub fn decimal_compute_zero<C: DecimalWord>() -> C {
C::decimal_compute_zero()
}
#[inline]
pub fn decimal_compute_scale<C: DecimalWord>() -> C {
C::pow10_compute_ct(C::DP)
}
#[inline]
pub fn decimal_compute_one<C: DecimalWord>() -> C {
decimal_compute_scale::<C>()
}
#[inline]
pub fn decimal_compute_from_int<C: DecimalWord>(n: i64) -> C {
C::decimal_compute_from_int(n)
}
#[cfg(test)]
#[cfg(test)]
#[allow(dead_code)]
#[inline]
fn wide_scale<C: DecimalWord>() -> C::Wide {
C::wide_scale()
}
#[inline]
#[allow(dead_code)]
pub(crate) fn pow10_i128(exp: u32) -> i128 {
let mut result: i128 = 1;
let mut i = 0;
while i < exp {
result *= 10;
i += 1;
}
result
}
#[inline]
#[allow(dead_code)]
pub(crate) fn pow10_compute_ct<C: DecimalWord>(exp: u8) -> C {
C::pow10_compute_ct(exp)
}
#[cfg(test)]
#[cfg(test)]
#[allow(dead_code)]
#[inline]
fn wide_div<C: DecimalWord>(a: C::Wide, b: C::Wide) -> C::Wide {
C::wide_div(a, b)
}
#[cfg(test)]
#[cfg(test)]
#[allow(dead_code)]
#[inline]
fn wide_div_scale<C: DecimalWord>(w: C::Wide) -> C::Wide {
C::wide_div_scale(w)
}
#[inline]
pub fn decimal_compute_add<C: DecimalWord>(a: C, b: C) -> C {
try_decimal_compute_add(a, b).expect("decimal compute tier: addition overflow")
}
#[inline]
pub fn try_decimal_compute_add<C: DecimalWord>(a: C, b: C) -> Result<C, OverflowDetected> {
a.checked_add_wide(b).ok_or(OverflowDetected::TierOverflow)
}
#[inline]
pub fn decimal_compute_sub<C: DecimalWord>(a: C, b: C) -> C {
try_decimal_compute_sub(a, b).expect("decimal compute tier: subtraction overflow")
}
#[inline]
pub fn try_decimal_compute_sub<C: DecimalWord>(a: C, b: C) -> Result<C, OverflowDetected> {
a.checked_add_wide(C::try_decimal_compute_neg(b)?).ok_or(OverflowDetected::TierOverflow)
}
#[inline]
pub fn try_decimal_compute_neg<C: DecimalWord>(a: C) -> Result<C, OverflowDetected> {
C::try_decimal_compute_neg(a)
}
#[inline]
pub fn decimal_compute_neg<C: DecimalWord>(a: C) -> C {
C::try_decimal_compute_neg(a).expect("decimal compute tier: negation overflow")
}
#[inline]
pub fn decimal_compute_halve<C: DecimalWord>(a: C) -> C {
a.shr_bits(1)
}
#[inline]
pub fn decimal_compute_abs<C: DecimalWord>(a: C) -> C {
if C::decimal_compute_is_negative(&a) {
decimal_compute_neg(a)
} else {
a
}
}
#[inline]
pub fn decimal_compute_is_zero<C: DecimalWord>(a: &C) -> bool {
C::decimal_compute_is_zero(a)
}
#[inline]
pub fn decimal_compute_is_negative<C: DecimalWord>(a: &C) -> bool {
C::decimal_compute_is_negative(a)
}
#[inline]
pub fn decimal_compute_cmp<C: DecimalWord>(a: &C, b: &C) -> std::cmp::Ordering {
a.cmp(b)
}
#[inline]
pub fn decimal_compute_mul<C: DecimalWord>(a: C, b: C) -> C {
try_decimal_compute_mul(a, b).expect("decimal_compute_mul: product outside the decimal compute tier")
}
#[inline]
pub fn try_decimal_compute_mul<C: DecimalWord>(a: C, b: C) -> Result<C, OverflowDetected> {
let product = C::wide_mul(a, b);
let scale = C::wide_scale();
let half_scale = C::wide_halve(scale);
let rounded = if C::wide_is_negative(&product) {
product - half_scale
} else {
product + half_scale
};
C::narrow_from_wide(C::wide_div_scale(rounded))
}
#[inline]
pub fn decimal_compute_div<C: DecimalWord>(a: C, b: C) -> Result<C, OverflowDetected> {
if C::decimal_compute_is_zero(&b) {
return Err(OverflowDetected::DomainError);
}
let numerator = C::wide_mul(a, decimal_compute_scale::<C>());
let divisor = C::widen_to_wide(b);
let half_divisor = C::wide_halve(if C::wide_is_negative(&divisor) { C::wide_neg(divisor) } else { divisor });
let result_neg = C::wide_is_negative(&numerator) ^ C::wide_is_negative(&divisor);
let num_abs = if C::wide_is_negative(&numerator) { C::wide_neg(numerator) } else { numerator };
let div_abs = if C::wide_is_negative(&divisor) { C::wide_neg(divisor) } else { divisor };
let rounded_num = num_abs + half_divisor;
let quotient = C::wide_div(rounded_num, div_abs);
let final_quot = if result_neg { C::wide_neg(quotient) } else { quotient };
C::narrow_from_wide(final_quot)
}
#[inline]
pub fn decimal_compute_div_int<C: DecimalWord>(a: C, n: u64) -> C {
C::decimal_compute_div_int(a, n)
}
#[inline]
pub fn decimal_upscale_to_compute<C: DecimalWord>(scaled: C::Narrow, storage_dp: u8) -> Result<C, OverflowDetected> {
if storage_dp == C::DP {
return Ok(C::binary_storage_to_compute_widen(scaled));
}
if storage_dp < C::DP {
let diff = C::DP - storage_dp;
if diff > C::DP {
return Err(OverflowDetected::Overflow);
}
let factor = C::pow10_compute_ct(diff);
let widened = C::binary_storage_to_compute_widen(scaled);
return Ok(widened * factor);
}
let diff = storage_dp - C::DP;
let divisor = C::pow10_compute_ct(diff);
let widened = C::binary_storage_to_compute_widen(scaled);
Ok(div_round_half_even(widened, divisor))
}
#[inline]
fn div_round_half_even<C: DecimalWord>(value: C, divisor: C) -> C {
let q = value.divmod(divisor).0;
let r = value - q * divisor; let r_abs = decimal_compute_abs(r);
let bump = match r_abs.cmp(&(divisor - r_abs)) {
std::cmp::Ordering::Less => false,
std::cmp::Ordering::Greater => true,
std::cmp::Ordering::Equal => decimal_compute_halve(q) + decimal_compute_halve(q) != q,
};
if !bump {
q
} else if C::decimal_compute_is_negative(&value) {
q - C::decimal_compute_unit()
} else {
q + C::decimal_compute_unit()
}
}
#[inline]
pub fn decimal_downscale_to_storage<C: DecimalWord>(compute_val: C, target_dp: u8) -> Result<C::Narrow, OverflowDetected> {
if target_dp > C::DP {
if target_dp > C::STORAGE_MAX_DP {
return Err(OverflowDetected::Overflow);
}
let diff = target_dp - C::DP;
let factor = C::pow10_compute_ct(diff);
let scaled = compute_val * factor;
return C::narrow_compute_to_storage(scaled);
}
if target_dp == C::DP {
return C::narrow_compute_to_storage(compute_val);
}
let diff = C::DP - target_dp;
let divisor = C::pow10_compute_ct(diff);
C::narrow_compute_to_storage(div_round_half_even(compute_val, divisor))
}
#[inline]
pub fn i128_to_compute<C: DecimalWord>(value: i128) -> C {
C::i128_to_compute(value)
}
pub fn i128_upscale_to_compute<C: DecimalWord>(value: i128, dp: u8) -> C {
C::try_i128_upscale_to_compute(value, dp)
.expect("DecimalFixed: value outside the decimal compute range")
}
pub fn try_i128_upscale_to_compute<C: DecimalWord>(value: i128, dp: u8) -> Result<C, OverflowDetected> {
C::try_i128_upscale_to_compute(value, dp)
}
pub fn decimal_compute_to_i128<C: DecimalWord>(value: C, dp: u8) -> i128 {
try_decimal_compute_to_i128::<C>(value, dp)
.expect("DecimalFixed: result outside the i128 storage range")
}
pub fn try_decimal_compute_to_i128<C: DecimalWord>(value: C, dp: u8) -> Result<i128, OverflowDetected> {
if dp > C::DP {
let factor = pow10_i128((dp - C::DP) as u32);
return C::compute_to_i128(value)?.checked_mul(factor).ok_or(OverflowDetected::TierOverflow);
}
let result = if dp < C::DP {
div_round_half_even(value, C::pow10_compute_ct(C::DP - dp))
} else {
value
};
C::compute_to_i128(result)
}
pub fn try_decimal_compute_to_i128_unless_ambiguous<C: DecimalWord>(value: C, dp: u8) -> Option<Result<i128, OverflowDetected>> {
if dp >= C::DP {
return Some(try_decimal_compute_to_i128::<C>(value, dp));
}
let guard = C::DP - dp;
if guard < 5 {
return None;
}
let margin = C::pow10_compute_ct(guard - 4);
let divisor = margin * C::from_i64(10_000);
let q = value.divmod(divisor).0;
let r = decimal_compute_abs(value - q * divisor); let d_minus_r = divisor - r;
let half_gap = if r < d_minus_r { d_minus_r - r } else { r - d_minus_r };
if r < margin || d_minus_r < margin || half_gap < margin + margin {
return None;
}
let rounded = if r > d_minus_r {
if C::decimal_compute_is_negative(&value) { q - C::decimal_compute_unit() } else { q + C::decimal_compute_unit() }
} else {
q
};
Some(C::compute_to_i128(rounded))
}
#[cfg(test)]
mod tests {
type ComputeStorage = crate::fixed_point::I256;
use super::*;
#[allow(unused_imports)]
use super::super::decimal_compute::*;
#[test]
fn scale_is_10_pow_38() {
let expected = pow10_compute_ct::<ComputeStorage>(38);
assert_eq!(decimal_compute_scale::<ComputeStorage>(), expected);
}
#[test]
fn one_times_one_is_one() {
let one = decimal_compute_one::<ComputeStorage>();
let result = decimal_compute_mul(one, one);
assert_eq!(result, one);
}
#[test]
fn two_times_three_is_six() {
let two = decimal_compute_from_int::<ComputeStorage>(2);
let three = decimal_compute_from_int::<ComputeStorage>(3);
let six = decimal_compute_from_int::<ComputeStorage>(6);
assert_eq!(decimal_compute_mul(two, three), six);
}
#[test]
fn six_div_three_is_two() {
let six = decimal_compute_from_int::<ComputeStorage>(6);
let three = decimal_compute_from_int::<ComputeStorage>(3);
let two = decimal_compute_from_int::<ComputeStorage>(2);
assert_eq!(decimal_compute_div(six, three).unwrap(), two);
}
#[test]
fn one_div_two_is_half() {
let one = decimal_compute_one::<ComputeStorage>();
let two = decimal_compute_from_int::<ComputeStorage>(2);
let half = decimal_compute_div(one, two).unwrap();
let expected = pow10_compute_ct::<ComputeStorage>(37) * I256::from_i128(5);
assert_eq!(half, expected);
}
#[test]
fn neg_and_abs() {
let two = decimal_compute_from_int::<ComputeStorage>(2);
let neg_two = decimal_compute_neg(two);
assert!(decimal_compute_is_negative(&neg_two));
assert_eq!(decimal_compute_abs(neg_two), two);
}
#[test]
fn upscale_dp_0_to_compute() {
let result = decimal_upscale_to_compute::<ComputeStorage>(42i128, 0).unwrap();
let expected = decimal_compute_from_int::<ComputeStorage>(42);
assert_eq!(result, expected);
}
#[test]
fn upscale_dp_1_to_compute() {
let result = decimal_upscale_to_compute::<ComputeStorage>(1i128, 1).unwrap();
let expected = pow10_compute_ct::<ComputeStorage>(37);
assert_eq!(result, expected);
}
#[test]
fn downscale_to_storage_dp_2() {
let one = decimal_compute_one::<ComputeStorage>();
let result = decimal_downscale_to_storage::<ComputeStorage>(one, 2).unwrap();
assert_eq!(result, 100i128);
}
#[test]
fn downscale_rounding() {
let val = I256::from_i128(125) * pow10_compute_ct::<ComputeStorage>(35);
assert_eq!(decimal_downscale_to_storage::<ComputeStorage>(val, 2).unwrap(), 12i128);
let val = I256::from_i128(135) * pow10_compute_ct::<ComputeStorage>(35);
assert_eq!(decimal_downscale_to_storage::<ComputeStorage>(val, 2).unwrap(), 14i128);
let val = I256::from_i128(-125) * pow10_compute_ct::<ComputeStorage>(35);
assert_eq!(decimal_downscale_to_storage::<ComputeStorage>(val, 2).unwrap(), -12i128);
}
}
#[cfg(test)]
mod wide_div_scale_tests {
use super::*;
#[allow(unused_imports)]
use super::super::decimal_compute::*;
use crate::fixed_point::{I1024, I512};
macro_rules! check {
($c:ty, $seed:expr) => {{
let mut state: u64 = $seed;
let scale = wide_scale::<$c>();
for case in 0..400 {
let mut w = wide_scale::<$c>();
let mut words = w.words;
let used = case % words.len() + 1;
for (i, word) in words.iter_mut().enumerate() {
state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
*word = if i < used { state } else { 0 };
}
let last = words.len() - 1;
words[last] &= 0x3FFF_FFFF_FFFF_FFFF;
w.words = words;
for v in [w, -w, w >> 1, -(w >> 3)] {
assert_eq!(wide_div_scale::<$c>(v), wide_div::<$c>(v, scale), "case {case}");
}
}
}};
}
#[test]
fn matches_the_general_division() {
check!(I512, 0x1234_5678_9ABC_DEF1);
check!(I1024, 0x1234_5678_9ABC_DEF1);
}
}