use num_bigint::BigInt;
use num_integer::Integer;
use axiolid_guarantees::Sign;
use crate::dyadic::Dyadic;
use crate::interval::Interval;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FixedInterval {
lo: BigInt,
hi: BigInt,
bits: u32,
}
fn ceil_shr(x: &BigInt, k: u32) -> BigInt {
-((-x) >> k)
}
impl FixedInterval {
#[must_use]
pub fn integer(value: i64, bits: u32) -> Self {
let v = BigInt::from(value) << bits;
Self {
lo: v.clone(),
hi: v,
bits,
}
}
#[must_use]
pub fn from_dyadic(value: &Dyadic, bits: u32) -> Self {
let shift = value.exponent() + i64::from(bits);
let m = value.mantissa();
if shift >= 0 {
let v = m << (shift as u64);
return Self {
lo: v.clone(),
hi: v,
bits,
};
}
let k = u32::try_from(-shift).unwrap_or(u32::MAX);
Self {
lo: m >> k,
hi: ceil_shr(m, k),
bits,
}
}
#[must_use]
pub fn from_f64(value: f64, bits: u32) -> Option<Self> {
Dyadic::try_from_f64(value).map(|d| Self::from_dyadic(&d, bits))
}
#[must_use]
pub fn bits(&self) -> u32 {
self.bits
}
fn same(&self, other: &Self) {
debug_assert_eq!(self.bits, other.bits, "fixed intervals of one precision");
}
#[must_use]
pub fn add(&self, other: &Self) -> Self {
self.same(other);
Self {
lo: &self.lo + &other.lo,
hi: &self.hi + &other.hi,
bits: self.bits,
}
}
#[must_use]
pub fn sub(&self, other: &Self) -> Self {
self.add(&other.neg())
}
#[must_use]
pub fn neg(&self) -> Self {
Self {
lo: -&self.hi,
hi: -&self.lo,
bits: self.bits,
}
}
#[must_use]
pub fn mul(&self, other: &Self) -> Self {
self.same(other);
let products = [
&self.lo * &other.lo,
&self.lo * &other.hi,
&self.hi * &other.lo,
&self.hi * &other.hi,
];
let lo = products.iter().min().expect("four products");
let hi = products.iter().max().expect("four products");
Self {
lo: lo >> self.bits,
hi: ceil_shr(hi, self.bits),
bits: self.bits,
}
}
#[must_use]
pub fn mul_int(&self, factor: i128) -> Self {
let factor = BigInt::from(factor);
let (a, b) = (&self.lo * &factor, &self.hi * &factor);
if a <= b {
Self {
lo: a,
hi: b,
bits: self.bits,
}
} else {
Self {
lo: b,
hi: a,
bits: self.bits,
}
}
}
#[must_use]
pub fn div_int(&self, divisor: u64) -> Self {
let d = BigInt::from(divisor.max(1));
Self {
lo: self.lo.div_floor(&d),
hi: -((-&self.hi).div_floor(&d)),
bits: self.bits,
}
}
#[must_use]
pub fn with_bits(&self, bits: u32) -> Self {
if bits >= self.bits {
let k = bits - self.bits;
return Self {
lo: &self.lo << k,
hi: &self.hi << k,
bits,
};
}
let k = self.bits - bits;
Self {
lo: &self.lo >> k,
hi: ceil_shr(&self.hi, k),
bits,
}
}
#[must_use]
pub fn sign(&self) -> Option<Sign> {
let zero = BigInt::from(0);
if self.lo > zero {
Some(Sign::Positive)
} else if self.hi < zero {
Some(Sign::Negative)
} else if self.lo == zero && self.hi == zero {
Some(Sign::Zero)
} else {
None
}
}
#[must_use]
pub fn enclosure(&self) -> Interval {
let exponent = -i64::from(self.bits);
let lo = Dyadic::from_parts(self.lo.clone(), exponent).enclosure();
let hi = Dyadic::from_parts(self.hi.clone(), exponent).enclosure();
Interval::from_bounds(lo.lo(), hi.hi())
}
#[must_use]
pub fn magnitude(&self) -> (f64, f64) {
let e = self.enclosure();
let upper = e.lo().abs().max(e.hi().abs());
let lower = if e.lo() > 0.0 {
e.lo()
} else if e.hi() < 0.0 {
-e.hi()
} else {
0.0
};
(lower, upper)
}
fn clamp_unit(mut self) -> Self {
let one = BigInt::from(1) << self.bits;
if self.hi > one {
self.hi = one.clone();
}
if self.lo < -&one {
self.lo = -one;
}
self
}
#[must_use]
pub fn sin_cos(x: &Dyadic, bits: u32) -> Option<(Self, Self)> {
let magnitude = x.to_f64().abs();
if magnitude > 2f64.powi(40) {
return None;
}
let mut halvings = 0u32;
while magnitude * 16.0 > 2f64.powi(halvings as i32) {
halvings += 1;
}
let w = bits + halvings + 32;
let y = if x.mantissa() == &BigInt::from(0) {
Self::integer(0, w)
} else {
let halved =
Dyadic::from_parts(x.mantissa().clone(), x.exponent() - i64::from(halvings));
Self::from_dyadic(&halved, w)
};
let y2 = y.mul(&y);
let one_ulp = BigInt::from(1);
let series = |mut term: Self, first: u64| {
let mut sum = term.clone();
let mut n = first;
loop {
term = term.mul(&y2).div_int((n + 1) * (n + 2)).neg();
n += 2;
let (_, size) = term.magnitude_ulps();
if size <= one_ulp {
let pad = size + &one_ulp;
sum.lo -= &pad;
sum.hi += &pad;
return sum;
}
sum = sum.add(&term);
}
};
let mut s = series(y.clone(), 1);
let mut c = series(Self::integer(1, w), 0);
for _ in 0..halvings {
let s2 = s.mul(&c).mul_int(2);
let c2 = Self::integer(1, w).sub(&s.mul(&s).mul_int(2));
s = s2.clamp_unit();
c = c2.clamp_unit();
}
Some((s.with_bits(bits), c.with_bits(bits)))
}
fn magnitude_ulps(&self) -> (BigInt, BigInt) {
let zero = BigInt::from(0);
let (a, b) = (self.lo.clone(), self.hi.clone());
let upper = if -&a > b { -&a } else { b.clone() };
let lower = if a > zero {
a
} else if b < zero {
-b
} else {
zero
};
(lower, upper)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn d(x: f64) -> Dyadic {
Dyadic::try_from_f64(x).unwrap()
}
#[test]
fn floors_and_ceilings_round_outward() {
let x = FixedInterval::from_f64(-0.1, 8).unwrap();
assert_eq!(
(x.lo.clone(), x.hi.clone()),
(BigInt::from(-26), BigInt::from(-25))
);
let p = x.mul(&x);
assert!(p.lo <= BigInt::from(2) && p.hi >= BigInt::from(3));
let q = FixedInterval::integer(7, 4).div_int(3);
assert_eq!((q.lo, q.hi), (BigInt::from(37), BigInt::from(38)));
}
#[test]
fn sine_and_cosine_of_one_hold_their_digits() {
let sin1: BigInt = "1352191738627887730370568382426388711294672994500390224088559"
.parse()
.unwrap();
let cos1: BigInt = "868232330700371202471720823065555340568207867417093696894962"
.parse()
.unwrap();
let (s, c) = FixedInterval::sin_cos(&d(1.0), 200).unwrap();
assert!(s.lo <= sin1 && sin1 < s.hi, "{s:?}");
assert!(c.lo <= cos1 && cos1 < c.hi, "{c:?}");
assert!(&s.hi - &s.lo < BigInt::from(8), "{s:?}");
assert!(&c.hi - &c.lo < BigInt::from(8), "{c:?}");
}
#[test]
fn large_and_tiny_angles_agree_with_f64() {
for x in [0.0, 1e-300, -3e-9, 0.75, -2.5, 7.0, 100.25, -12345.678] {
let (s, c) = FixedInterval::sin_cos(&d(x), 120).unwrap();
let (es, ec) = (s.enclosure(), c.enclosure());
assert!(
es.lo() <= x.sin() + 1e-15 && x.sin() - 1e-15 <= es.hi(),
"{x}"
);
assert!(
ec.lo() <= x.cos() + 1e-15 && x.cos() - 1e-15 <= ec.hi(),
"{x}"
);
let one = s.mul(&s).add(&c.mul(&c));
let unit = BigInt::from(1) << 120u32;
assert!(one.lo <= unit && unit <= one.hi, "{x}: {one:?}");
assert!(&one.hi - &one.lo < BigInt::from(64), "{x}: {one:?}");
}
assert!(FixedInterval::sin_cos(&d(1e13), 64).is_none());
}
#[test]
fn signs_are_certain_or_withheld() {
assert_eq!(FixedInterval::integer(0, 10).sign(), Some(Sign::Zero));
let (s, _) = FixedInterval::sin_cos(&d(1e-40), 64).unwrap();
assert_eq!(s.sign(), None);
let (s, _) = FixedInterval::sin_cos(&d(1e-40), 200).unwrap();
assert_eq!(s.sign(), Some(Sign::Positive));
}
}