use std::ops::{Add, Div, Mul, Neg, Sub};
use super::d256::{divmod_d256_by_d256, mul_i128_to_d256, D256};
use super::decimal_fixed::DecimalFixed;
use crate::fixed_point::core_types::errors::OverflowDetected;
#[inline]
fn st_add(a: i128, b: i128) -> Result<i128, OverflowDetected> {
a.checked_add(b).ok_or(OverflowDetected::TierOverflow)
}
#[inline]
fn st_sub(a: i128, b: i128) -> Result<i128, OverflowDetected> {
a.checked_sub(b).ok_or(OverflowDetected::TierOverflow)
}
#[inline]
fn st_neg(a: i128) -> Result<i128, OverflowDetected> {
a.checked_neg().ok_or(OverflowDetected::TierOverflow)
}
#[inline]
fn d256_checked_add(a: D256, b: D256) -> Result<D256, OverflowDetected> {
let sum = a + b;
if a.is_negative() == b.is_negative() && sum.is_negative() != a.is_negative() {
Err(OverflowDetected::TierOverflow)
} else {
Ok(sum)
}
}
#[inline]
fn d256_to_i128(v: D256) -> Result<i128, OverflowDetected> {
if v.fits_in_i128() { Ok(v.as_i128()) } else { Err(OverflowDetected::TierOverflow) }
}
#[inline]
fn bit_length(v: D256) -> u32 {
for i in (0..4).rev() {
if v.words[i] != 0 {
return i as u32 * 64 + (64 - v.words[i].leading_zeros());
}
}
0
}
#[inline]
fn pow2(e: u32) -> D256 {
debug_assert!(e <= 127);
let v = 1u128 << e;
D256::from_words([v as u64, (v >> 64) as u64, 0, 0])
}
#[inline]
fn narrow<const DECIMALS: u8>(p: D256) -> Result<(i128, i128), OverflowDetected> {
let scale = D256::from_i128(DecimalFixed::<DECIMALS>::SCALE);
let (q, r) = divmod_d256_by_d256(p, scale);
let one = D256::from_i128(1);
let (f, c) = if r.is_zero() {
(q, q)
} else if p.is_negative() {
(q - one, q)
} else {
(q, q + one)
};
Ok((d256_to_i128(f)?, d256_to_i128(c)?))
}
#[inline]
fn directed_divide<const DECIMALS: u8>(a: i128, b: i128) -> Result<(i128, i128), OverflowDetected> {
if b == 0 {
return Err(OverflowDetected::DivisionByZero);
}
let num = mul_i128_to_d256(a, DecimalFixed::<DECIMALS>::SCALE);
let (q, r) = divmod_d256_by_d256(num, D256::from_i128(b));
let one = D256::from_i128(1);
let (f, c) = if r.is_zero() {
(q, q)
} else if (a < 0) == (b < 0) {
(q, q + one)
} else {
(q - one, q)
};
Ok((d256_to_i128(f)?, d256_to_i128(c)?))
}
fn certified_sqrt<const DECIMALS: u8>(x: i128) -> Result<(i128, i128), OverflowDetected> {
debug_assert!(x >= 0);
let n = mul_i128_to_d256(x, DecimalFixed::<DECIMALS>::SCALE);
if n.is_zero() {
return Ok((0, 0));
}
let two = D256::from_i128(2);
let mut k = pow2((bit_length(n) + 1) / 2);
loop {
let (q, _) = divmod_d256_by_d256(n, k);
let next = (k + q) / two;
if next >= k {
break;
}
k = next;
}
let mut k = d256_to_i128(k)?;
let mut steps = 0u32;
while mul_i128_to_d256(k, k) > n {
k -= 1;
steps += 1;
}
loop {
let next = st_add(k, 1)?;
if mul_i128_to_d256(next, next) <= n {
k = next;
steps += 1;
} else {
break;
}
}
debug_assert!(steps <= 2, "certified_sqrt: Newton candidate was {steps} units off");
let ceil = if mul_i128_to_d256(k, k) == n { k } else { st_add(k, 1)? };
Ok((k, ceil))
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct DecimalInterval<const DECIMALS: u8> {
lo: DecimalFixed<DECIMALS>,
hi: DecimalFixed<DECIMALS>,
}
impl<const DECIMALS: u8> DecimalInterval<DECIMALS> {
#[inline]
pub fn point(x: DecimalFixed<DECIMALS>) -> Self {
DecimalInterval { lo: x, hi: x }
}
#[inline]
pub fn new(lo: DecimalFixed<DECIMALS>, hi: DecimalFixed<DECIMALS>) -> Self {
Self::try_new(lo, hi).expect("DecimalInterval::new: lo > hi")
}
#[inline]
pub fn try_new(lo: DecimalFixed<DECIMALS>, hi: DecimalFixed<DECIMALS>) -> Result<Self, OverflowDetected> {
if lo > hi {
return Err(OverflowDetected::InvalidInput);
}
Ok(DecimalInterval { lo, hi })
}
#[inline]
fn from_raw(lo: i128, hi: i128) -> Self {
debug_assert!(lo <= hi, "DecimalInterval invariant violated: lo > hi");
DecimalInterval { lo: DecimalFixed::from_raw(lo), hi: DecimalFixed::from_raw(hi) }
}
#[inline]
pub fn lo(self) -> DecimalFixed<DECIMALS> { self.lo }
#[inline]
pub fn hi(self) -> DecimalFixed<DECIMALS> { self.hi }
#[inline]
pub fn width(self) -> DecimalFixed<DECIMALS> {
DecimalFixed::from_raw(st_sub(self.hi.raw_value(), self.lo.raw_value()).expect("DecimalInterval::width: overflow"))
}
#[inline]
pub fn is_point(self) -> bool { self.lo == self.hi }
#[inline]
pub fn contains(self, x: DecimalFixed<DECIMALS>) -> bool { self.lo <= x && x <= self.hi }
#[inline]
pub fn contains_zero(self) -> bool { self.contains(DecimalFixed::ZERO) }
#[inline]
pub fn is_certainly_positive(self) -> bool { self.lo > DecimalFixed::ZERO }
#[inline]
pub fn is_certainly_negative(self) -> bool { self.hi < DecimalFixed::ZERO }
pub fn try_add(self, rhs: Self) -> Result<Self, OverflowDetected> {
Ok(Self::from_raw(
st_add(self.lo.raw_value(), rhs.lo.raw_value())?,
st_add(self.hi.raw_value(), rhs.hi.raw_value())?,
))
}
pub fn try_sub(self, rhs: Self) -> Result<Self, OverflowDetected> {
Ok(Self::from_raw(
st_sub(self.lo.raw_value(), rhs.hi.raw_value())?,
st_sub(self.hi.raw_value(), rhs.lo.raw_value())?,
))
}
pub fn try_neg(self) -> Result<Self, OverflowDetected> {
Ok(Self::from_raw(st_neg(self.hi.raw_value())?, st_neg(self.lo.raw_value())?))
}
pub fn try_mul(self, rhs: Self) -> Result<Self, OverflowDetected> {
let p = [
mul_i128_to_d256(self.lo.raw_value(), rhs.lo.raw_value()),
mul_i128_to_d256(self.lo.raw_value(), rhs.hi.raw_value()),
mul_i128_to_d256(self.hi.raw_value(), rhs.lo.raw_value()),
mul_i128_to_d256(self.hi.raw_value(), rhs.hi.raw_value()),
];
let mut mn = p[0];
let mut mx = p[0];
for q in &p[1..] {
if *q < mn { mn = *q; }
if *q > mx { mx = *q; }
}
Ok(Self::from_raw(narrow::<DECIMALS>(mn)?.0, narrow::<DECIMALS>(mx)?.1))
}
pub fn try_div(self, rhs: Self) -> Result<Self, OverflowDetected> {
if rhs.contains_zero() {
return Err(OverflowDetected::DivisionByZero);
}
let corners = [
directed_divide::<DECIMALS>(self.lo.raw_value(), rhs.lo.raw_value())?,
directed_divide::<DECIMALS>(self.lo.raw_value(), rhs.hi.raw_value())?,
directed_divide::<DECIMALS>(self.hi.raw_value(), rhs.lo.raw_value())?,
directed_divide::<DECIMALS>(self.hi.raw_value(), rhs.hi.raw_value())?,
];
let mut lo = corners[0].0;
let mut hi = corners[0].1;
for (f, c) in &corners[1..] {
if *f < lo { lo = *f; }
if *c > hi { hi = *c; }
}
Ok(Self::from_raw(lo, hi))
}
pub fn try_sqrt(self) -> Result<Self, OverflowDetected> {
if self.lo < DecimalFixed::ZERO {
return Err(OverflowDetected::DomainError);
}
let (lo, _) = certified_sqrt::<DECIMALS>(self.lo.raw_value())?;
let (_, hi) = certified_sqrt::<DECIMALS>(self.hi.raw_value())?;
Ok(Self::from_raw(lo, hi))
}
pub fn try_dot(a: &[DecimalFixed<DECIMALS>], b: &[DecimalFixed<DECIMALS>]) -> Result<Self, OverflowDetected> {
assert_eq!(a.len(), b.len(), "DecimalInterval::dot: length mismatch");
let mut acc = D256::zero();
for i in 0..a.len() {
acc = d256_checked_add(acc, mul_i128_to_d256(a[i].raw_value(), b[i].raw_value()))?;
}
let (lo, hi) = narrow::<DECIMALS>(acc)?;
Ok(Self::from_raw(lo, hi))
}
pub fn sqrt(self) -> Self {
self.try_sqrt().expect("DecimalInterval::sqrt: domain error or overflow")
}
pub fn dot(a: &[DecimalFixed<DECIMALS>], b: &[DecimalFixed<DECIMALS>]) -> Self {
Self::try_dot(a, b).expect("DecimalInterval::dot: overflow")
}
}
impl<const DECIMALS: u8> Add for DecimalInterval<DECIMALS> {
type Output = Self;
#[inline]
fn add(self, rhs: Self) -> Self { self.try_add(rhs).expect("DecimalInterval: addition overflow") }
}
impl<const DECIMALS: u8> Sub for DecimalInterval<DECIMALS> {
type Output = Self;
#[inline]
fn sub(self, rhs: Self) -> Self { self.try_sub(rhs).expect("DecimalInterval: subtraction overflow") }
}
impl<const DECIMALS: u8> Mul for DecimalInterval<DECIMALS> {
type Output = Self;
#[inline]
fn mul(self, rhs: Self) -> Self { self.try_mul(rhs).expect("DecimalInterval: multiplication overflow") }
}
impl<const DECIMALS: u8> Div for DecimalInterval<DECIMALS> {
type Output = Self;
#[inline]
fn div(self, rhs: Self) -> Self { self.try_div(rhs).expect("DecimalInterval: division by an interval containing zero, or overflow") }
}
impl<const DECIMALS: u8> Neg for DecimalInterval<DECIMALS> {
type Output = Self;
#[inline]
fn neg(self) -> Self { self.try_neg().expect("DecimalInterval: negation overflow") }
}
impl<const DECIMALS: u8> From<DecimalFixed<DECIMALS>> for DecimalInterval<DECIMALS> {
#[inline]
fn from(x: DecimalFixed<DECIMALS>) -> Self { DecimalInterval::point(x) }
}