Skip to main content

implicit_predicates_oracle/
dyadic.rs

1//! Exact dyadic rational numbers, `mantissa * 2^exponent`.
2
3use core::cmp::Ordering;
4use core::ops::{Add, Mul, Neg, Sub};
5
6use num_bigint::{BigInt, Sign};
7
8/// An exact dyadic rational: an arbitrary-precision integer mantissa times a
9/// power of two.
10///
11/// Every finite `f64` converts exactly, and addition, subtraction and
12/// multiplication never round or overflow. Equality and ordering compare
13/// values, not representations.
14///
15/// # Examples
16///
17/// ```
18/// use implicit_predicates_oracle::Dyadic;
19///
20/// let tenth = Dyadic::from_f64(0.1);
21/// let three_tenths = &(&tenth + &tenth) + &tenth;
22/// // 0.1 + 0.1 + 0.1 is not 0.3 in binary, and the oracle sees the difference.
23/// assert_eq!(three_tenths.sign(), 1);
24/// assert!(three_tenths > Dyadic::from_f64(0.3));
25/// ```
26#[derive(Clone, Debug)]
27pub struct Dyadic {
28    mant: BigInt,
29    exp: i64,
30}
31
32impl Dyadic {
33    /// Converts a finite `f64` exactly. Negative zero converts to zero.
34    ///
35    /// # Panics
36    ///
37    /// Panics if `x` is NaN or infinite.
38    ///
39    /// # Examples
40    ///
41    /// ```
42    /// use implicit_predicates_oracle::Dyadic;
43    ///
44    /// let two = &Dyadic::from_f64(1.0) + &Dyadic::from_f64(1.0);
45    /// assert_eq!(two, Dyadic::from_f64(2.0));
46    /// ```
47    pub fn from_f64(x: f64) -> Dyadic {
48        assert!(x.is_finite(), "Dyadic::from_f64: non-finite input");
49        let bits = x.to_bits();
50        let biased_exp = ((bits >> 52) & 0x7ff) as i64;
51        let fraction = bits & ((1u64 << 52) - 1);
52        let (magnitude, exp) = if biased_exp == 0 {
53            // Subnormal (or zero): no implicit bit.
54            (fraction, -1074)
55        } else {
56            (fraction | (1u64 << 52), biased_exp - 1075)
57        };
58        let sign = if bits >> 63 == 1 {
59            Sign::Minus
60        } else {
61            Sign::Plus
62        };
63        Dyadic {
64            mant: BigInt::from_biguint(sign, magnitude.into()),
65            exp,
66        }
67    }
68
69    /// Returns the exact sum of all the given values.
70    ///
71    /// # Panics
72    ///
73    /// Panics if any value is NaN or infinite.
74    ///
75    /// # Examples
76    ///
77    /// ```
78    /// use implicit_predicates_oracle::Dyadic;
79    ///
80    /// assert_eq!(Dyadic::sum_of(&[1.0, 2.0, 4.0]), Dyadic::from_f64(7.0));
81    /// assert!(Dyadic::sum_of(&[]).is_zero());
82    /// ```
83    pub fn sum_of(xs: &[f64]) -> Dyadic {
84        xs.iter()
85            .fold(Dyadic::from_f64(0.0), |acc, &x| &acc + &Dyadic::from_f64(x))
86    }
87
88    /// Returns `-1`, `0` or `1` according to the sign of the value.
89    ///
90    /// # Examples
91    ///
92    /// ```
93    /// use implicit_predicates_oracle::Dyadic;
94    ///
95    /// assert_eq!(Dyadic::from_f64(-3.5).sign(), -1);
96    /// assert_eq!(Dyadic::from_f64(0.0).sign(), 0);
97    /// assert_eq!(Dyadic::from_f64(3.5).sign(), 1);
98    /// ```
99    pub fn sign(&self) -> i8 {
100        match self.mant.sign() {
101            Sign::Minus => -1,
102            Sign::NoSign => 0,
103            Sign::Plus => 1,
104        }
105    }
106
107    /// Returns `true` if the value is exactly zero.
108    ///
109    /// # Examples
110    ///
111    /// ```
112    /// use implicit_predicates_oracle::Dyadic;
113    ///
114    /// assert!(Dyadic::from_f64(-0.0).is_zero());
115    /// assert!(!Dyadic::from_f64(1e-300).is_zero());
116    /// ```
117    pub fn is_zero(&self) -> bool {
118        self.sign() == 0
119    }
120
121    /// Mantissa scaled so that it is expressed with exponent `exp`, which
122    /// must not exceed `self.exp`.
123    fn mantissa_at(&self, exp: i64) -> BigInt {
124        let shift = usize::try_from(self.exp - exp).expect("exponent difference fits in usize");
125        &self.mant << shift
126    }
127}
128
129impl Add for &Dyadic {
130    type Output = Dyadic;
131
132    fn add(self, rhs: &Dyadic) -> Dyadic {
133        let exp = self.exp.min(rhs.exp);
134        Dyadic {
135            mant: self.mantissa_at(exp) + rhs.mantissa_at(exp),
136            exp,
137        }
138    }
139}
140
141impl Sub for &Dyadic {
142    type Output = Dyadic;
143
144    fn sub(self, rhs: &Dyadic) -> Dyadic {
145        let exp = self.exp.min(rhs.exp);
146        Dyadic {
147            mant: self.mantissa_at(exp) - rhs.mantissa_at(exp),
148            exp,
149        }
150    }
151}
152
153impl Mul for &Dyadic {
154    type Output = Dyadic;
155
156    fn mul(self, rhs: &Dyadic) -> Dyadic {
157        Dyadic {
158            mant: &self.mant * &rhs.mant,
159            exp: self.exp + rhs.exp,
160        }
161    }
162}
163
164impl Neg for &Dyadic {
165    type Output = Dyadic;
166
167    fn neg(self) -> Dyadic {
168        Dyadic {
169            mant: -&self.mant,
170            exp: self.exp,
171        }
172    }
173}
174
175/// Implements an operator for owned operands by delegating to the
176/// reference implementation.
177macro_rules! forward_owned_binary_op {
178    ($trait:ident, $method:ident) => {
179        impl $trait for Dyadic {
180            type Output = Dyadic;
181
182            fn $method(self, rhs: Dyadic) -> Dyadic {
183                $trait::$method(&self, &rhs)
184            }
185        }
186    };
187}
188
189forward_owned_binary_op!(Add, add);
190forward_owned_binary_op!(Sub, sub);
191forward_owned_binary_op!(Mul, mul);
192
193impl Neg for Dyadic {
194    type Output = Dyadic;
195
196    fn neg(self) -> Dyadic {
197        -&self
198    }
199}
200
201impl PartialEq for Dyadic {
202    fn eq(&self, other: &Dyadic) -> bool {
203        self.cmp(other) == Ordering::Equal
204    }
205}
206
207impl Eq for Dyadic {}
208
209impl PartialOrd for Dyadic {
210    fn partial_cmp(&self, other: &Dyadic) -> Option<Ordering> {
211        Some(self.cmp(other))
212    }
213}
214
215impl Ord for Dyadic {
216    fn cmp(&self, other: &Dyadic) -> Ordering {
217        (self - other).sign().cmp(&0)
218    }
219}