Skip to main content

rustymath_core/
recipe.rs

1//! Formulas that rustymath's kernels compute and basedpl's JIT compiles, each written once over the operations it uses (`Ops`): tolerant closeness, comparison (each item, or through the range of floats equal to one number), floor, ceiling and the residue; Mathish division, sign, the reciprocal and `π`'s two forms; and the powers that `Exponent` routes off the general route, with the square root they take for halves. `Floats` runs a recipe on floats, as the kernels do. A compiler's `Ops` records the same operations as steps, so both routes round alike, step for step.
2use crate::{Exponent, Float, float::sealed::Core, tolerant::Band};
3/// `Ops`' integer methods on bit patterns held in `u64`, `bits` wide, which `NativeOps` and `IntegerOps` share.
4macro_rules! u64_integers {
5    ($bits:expr) => {
6        #[inline(always)]
7        fn integer(&self, x: i64) -> u64 {
8            x as u64
9        }
10        #[inline(always)]
11        fn integer_op(&self, op: Integer, x: u64, y: u64) -> u64 {
12            crate::recipe::integer_op(op, x, y, $bits)
13        }
14        #[inline(always)]
15        fn integer_compare(&self, op: Cmp, x: u64, y: u64) -> bool {
16            crate::recipe::integer_compare(op, x, y)
17        }
18        #[inline(always)]
19        fn integer_select(&self, test: bool, x: u64, y: u64) -> u64 {
20            if test { x } else { y }
21        }
22    };
23}
24/// The comparison and Boolean `Ops` methods that `NativeOps` and `IntegerOps` share, on plain values and `bool` masks.
25macro_rules! plain_logic {
26    ($t:ty) => {
27        #[inline(always)]
28        fn compare(&self, c: Cmp, x: $t, y: $t) -> bool {
29            match c {
30                Cmp::Less => x < y,
31                Cmp::LessEqual => x <= y,
32                Cmp::Equal => x == y,
33            }
34        }
35        #[inline(always)]
36        fn and(&self, x: bool, y: bool) -> bool {
37            x & y
38        }
39        #[inline(always)]
40        fn or(&self, x: bool, y: bool) -> bool {
41            x | y
42        }
43        #[inline(always)]
44        fn not(&self, x: bool) -> bool {
45            !x
46        }
47        #[inline(always)]
48        fn select(&self, test: bool, x: $t, y: $t) -> $t {
49            if test { x } else { y }
50        }
51    };
52}
53mod integer;
54pub use crate::arith::Overflowing;
55pub use integer::IntegerOps;
56
57/// An elementwise operation of one value that a recipe uses, named by id: an `Ops` implementor computes each in one match (`Ops::unary`).
58#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)]
59pub enum Unary {
60    Negate,
61    Magnitude,
62    Sqrt,
63    Floor,
64    Ln,
65    Sin,
66    Cos,
67    Sinh,
68    Cosh,
69    /// √(1 - x²).
70    Sqrt1mx2,
71    /// √(1 + x²).
72    Sqrt1px2,
73    /// ψ(x).
74    Digamma,
75    /// e^x.
76    Exp,
77    /// ln |Γ(x)|.
78    Lgamma,
79    /// 1 - x: the negation of a truth value held as 0 or 1, as a Boolean is.
80    Not,
81}
82
83/// An elementwise operation of two values that a recipe uses, named by id (`Ops::binary`).
84#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)]
85pub enum Binary {
86    Add,
87    Subtract,
88    Multiply,
89    Divide,
90    /// Runtime power retains the same exponent dispatch as the native kernel.
91    Power,
92    /// `x` where `x > y`, otherwise `y`: `y` where either is NaN.
93    Larger,
94    /// `x` where `x < y`, otherwise `y`: `y` where either is NaN.
95    Smaller,
96    /// C(n, k), the binomial coefficient, with `n` first.
97    Binom,
98    /// ∂C(n, k)/∂n, with `n` first.
99    BinomDn,
100    /// ∂C(n, k)/∂k, with `n` first.
101    BinomDk,
102    /// ψ⁽ⁿ⁾(x), the polygamma function, with its whole order `n` first.
103    Polygamma,
104    /// The bitwise and of two integers, which only integer values take (`Ops::integral`).
105    BitAnd,
106}
107
108/// A comparison that a recipe uses, which gives a truth value (`Ops::compare`).
109#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)]
110pub enum Cmp { Less, LessEqual, Equal }
111
112#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)]
113pub enum Integer { Add, Subtract, And, Or, Xor, ShiftLeft, ShiftRight, SignedShiftRight }
114
115/// `op` on the bit patterns `x` and `y` of integers `bits` wide. Sums and differences wrap. Left and signed right shifts follow `shifted`. An unsigned right shift reads `x` as `bits` wide and gives 0 for a count of 64 or more. `NativeOps` and `IntegerOps` both compute through it.
116#[inline(always)]
117pub(crate) fn integer_op(op: Integer, x: u64, y: u64, bits: u32) -> u64 {
118    match op {
119        Integer::Add => x.wrapping_add(y), Integer::Subtract => x.wrapping_sub(y), Integer::And => x & y, Integer::Or => x | y, Integer::Xor => x ^ y,
120        Integer::ShiftLeft => shifted(x, y as i64), Integer::ShiftRight => { if y >= 64 { 0 } else { (x & (u64::MAX >> (64 - bits))) >> y } }
121        Integer::SignedShiftRight => shifted(x, (y as i64).saturating_neg())
122    }
123}
124
125/// Comparison `op` of the bit patterns `x` and `y`, read as signed 64-bit integers.
126#[inline(always)]
127pub(crate) fn integer_compare(op: Cmp, x: u64, y: u64) -> bool {
128    match op { Cmp::Less => (x as i64) < y as i64, Cmp::LessEqual => (x as i64) <= y as i64, Cmp::Equal => x == y }
129}
130
131/// `x`'s bits shifted left by `count`, or right with the sign filled in for a negative count. A count of 64 or more saturates: to 0 leftward, and to the sign rightward. Items hold their sign extended to 64 bits, so a count past an item's own width saturates the same way once the result narrows.
132#[inline(always)]
133fn shifted(x: u64, count: i64) -> u64 { if count >= 64 { 0 } else if count >= 0 { x << count } else { ((x as i64) >> count.unsigned_abs().min(63)) as u64 } }
134
135/// The named methods of `Ops`, each the operation of its id. Inline metadata keeps each constant operation id visible across crate boundaries, so native item loops can specialize and vectorize.
136macro_rules! named {
137    (unary: $($u:ident $uid:ident),+; binary: $($b:ident $bid:ident),+; compare: $($c:ident $cid:ident),+) => {
138        $( #[inline] fn $u(&self, x: Self::V) -> Self::V { self.unary(Unary::$uid, x) } )+
139        $( #[inline] fn $b(&self, x: Self::V, y: Self::V) -> Self::V { self.binary(Binary::$bid, x, y) } )+
140        $( #[inline] fn $c(&self, x: Self::V, y: Self::V) -> Self::B { self.compare(Cmp::$cid, x, y) } )+
141    };
142}
143
144/// The operations that a recipe uses, on values `V` and truth values `B`, at float type `F`. Each rounds as IEEE's operation, or rustymath's function of its name, does at `F`'s width. An implementor computes the operations by id, in `unary`, `binary` and `compare`, and recipes call them by their names.
145pub trait Ops<F: Float> {
146    /// A value.
147    type V: Copy;
148    /// A truth value.
149    type B: Copy;
150    /// Integer bits at the resolved working width.
151    type I: Copy;
152    fn width(&self) -> crate::buffer::FloatWidth;
153    fn fused(&self) -> bool;
154    /// A binary64 temporary for short mixed-width reductions.
155    type W: Copy;
156    fn widen(&self, x: Self::V) -> Self::W;
157    fn wide_number(&self, x: f64) -> Self::W;
158    fn wide_multiply_add(&self, x: Self::W, y: Self::W, z: Self::W) -> Self::W;
159    fn narrow(&self, x: Self::W) -> Self::V;
160    fn integer(&self, x: i64) -> Self::I;
161    fn integer_op(&self, op: Integer, x: Self::I, y: Self::I) -> Self::I;
162    fn integer_compare(&self, op: Cmp, x: Self::I, y: Self::I) -> Self::B;
163    fn integer_select(&self, test: Self::B, x: Self::I, y: Self::I) -> Self::I;
164    fn to_integer(&self, x: Self::V) -> Self::I;
165    fn from_integer(&self, x: Self::I) -> Self::V;
166    fn to_bits(&self, x: Self::V) -> Self::I;
167    fn from_bits(&self, x: Self::I) -> Self::V;
168    /// The number `x`.
169    fn number(&self, x: F) -> Self::V;
170    fn unary(&self, op: Unary, x: Self::V) -> Self::V;
171    fn binary(&self, op: Binary, x: Self::V, y: Self::V) -> Self::V;
172    fn compare(&self, c: Cmp, x: Self::V, y: Self::V) -> Self::B;
173    /// `x·y + z`, rounded once where the target fuses multiply-add (`FUSED`), as rustymath's `mla` gives it.
174    fn multiply_add(&self, x: Self::V, y: Self::V, z: Self::V) -> Self::V;
175    fn overflow(&self, op: Binary, x: Self::V, y: Self::V) -> Self::B;
176    fn and(&self, x: Self::B, y: Self::B) -> Self::B;
177    fn or(&self, x: Self::B, y: Self::B) -> Self::B;
178    fn not(&self, x: Self::B) -> Self::B;
179    /// `x` where `m` holds, otherwise `y`. `∂`'s recorder runs a recipe on whole values, where `m` and each array case must have one layout: build them from the same arguments.
180    fn select(&self, m: Self::B, x: Self::V, y: Self::V) -> Self::V;
181    /// 1 where `m` holds, otherwise 0.
182    #[inline]
183    fn one_where(&self, m: Self::B) -> Self::V { self.select(m, self.number(F::one()), self.number(F::zero())) }
184    /// `v` where it's one number that every item shares, known before any item is read, such as the 3 of `x^3`. A recipe can pick a cheaper formula by it. `None` where `v` varies by item.
185    #[inline]
186    fn constant(&self, _v: Self::V) -> Option<F> { None }
187    /// Whether values are integers, as a compiler's values at an integer type are. A recipe for an integer object asks before taking an integer-only step (`Binary::BitAnd`). Floats never are.
188    #[inline]
189    fn integral(&self) -> bool { false }
190    /// Captured tolerance; recording backends return its runtime setting slot.
191    #[inline]
192    fn tolerance(&self, fallback: F) -> Self::V { self.number(fallback) }
193    /// The sign of `x`, as [`sign`] gives it.
194    #[inline]
195    fn sign(&self, x: Self::V) -> Self::V { sign(self, x) }
196    /// `1 ÷ x`, as [`reciprocal`] gives it.
197    #[inline]
198    fn reciprocal(&self, x: Self::V) -> Self::V { reciprocal(self, x) }
199    /// `x` raised to `k`, a constant that `Exponent::of` routes off the general route, as [`power`] gives it.
200    #[inline]
201    fn power(&self, k: F, x: Self::V) -> Self::V { power(self, Exponent::of(k.wide(), self.fused()), x).expect("an exponent off the general route") }
202    /// The real part of `x^y` through the existing power formula.
203    #[inline]
204    fn real_power(&self, x: Self::V, y: Self::V) -> Self::V { crate::pow::real_formula(self, x, y) }
205    /// The partial in `x` of `x^y = r`; `halves` permits constant half-integer powers.
206    #[inline]
207    fn power_slope(&self, x: Self::V, y: Self::V, r: Self::V, halves: bool) -> Self::V { crate::derivative::power_slope(self, x, y, r, halves) }
208    named! {
209        unary: negate Negate, magnitude Magnitude, sqrt Sqrt, floor Floor, ln Ln, sin Sin, cos Cos, sinh Sinh, cosh Cosh, sqrt1mx2 Sqrt1mx2, sqrt1px2 Sqrt1px2, digamma Digamma, exp Exp, lgamma Lgamma, complement Not;
210        binary: add Add, subtract Subtract, multiply Multiply, divide Divide, larger Larger, smaller Smaller, binom Binom, binom_dn BinomDn, binom_dk BinomDk, polygamma Polygamma, bit_and BitAnd;
211        compare: less Less, less_equal LessEqual, equal Equal
212    }
213}
214
215/// Conservative range interpretation of the shared predicates.
216pub struct RangeOps(pub crate::buffer::DType);
217impl RangeOps {
218    fn compare(op: Cmp, x: crate::domain::Range, y: crate::domain::Range) -> Option<bool> {
219        if x.is_empty() || y.is_empty() { return None; }
220        match op {
221            Cmp::Less if x.hi < y.lo => Some(true), Cmp::Less if x.lo >= y.hi => Some(false), Cmp::LessEqual if x.hi <= y.lo => Some(true),
222            Cmp::LessEqual if x.lo > y.hi => Some(false), Cmp::Equal if x.constant().is_some() && x == y => Some(true),
223            Cmp::Equal if x.hi < y.lo || y.hi < x.lo => Some(false), _ => None
224        }
225    }
226    fn selected(test: Option<bool>, x: crate::domain::Range, y: crate::domain::Range) -> crate::domain::Range {
227        match test { Some(true) => x, Some(false) => y, None => x.join(y) }
228    }
229}
230impl<F: Float> Ops<F> for RangeOps {
231    type V = crate::domain::Range;
232    type B = Option<bool>;
233    type I = crate::domain::Range;
234    type W = crate::domain::Range;
235    fn width(&self) -> crate::buffer::FloatWidth { self.0.numpy_float() }
236    fn fused(&self) -> bool { crate::FUSED }
237    fn integral(&self) -> bool { self.0.is_integral() }
238    fn widen(&self, _: Self::V) -> Self::W { crate::domain::Range::ANY }
239    fn wide_number(&self, x: f64) -> Self::W { crate::domain::Range::of(Some(x)) }
240    fn wide_multiply_add(&self, _: Self::W, _: Self::W, _: Self::W) -> Self::W { crate::domain::Range::ANY }
241    fn narrow(&self, _: Self::W) -> Self::V { crate::domain::Range::ANY }
242    fn integer(&self, x: i64) -> Self::I { crate::domain::Range::of((x.unsigned_abs() <= 1 << 53).then_some(x as f64)) }
243    fn integer_op(&self, _: Integer, _: Self::I, _: Self::I) -> Self::I { crate::domain::Range::ANY }
244    fn integer_compare(&self, op: Cmp, x: Self::I, y: Self::I) -> Self::B { Self::compare(op, x, y) }
245    fn integer_select(&self, test: Self::B, x: Self::I, y: Self::I) -> Self::I { Self::selected(test, x, y) }
246    fn to_integer(&self, _: Self::V) -> Self::I { crate::domain::Range::ANY }
247    fn from_integer(&self, _: Self::I) -> Self::V { crate::domain::Range::ANY }
248    fn to_bits(&self, _: Self::V) -> Self::I { crate::domain::Range::ANY }
249    fn from_bits(&self, _: Self::I) -> Self::V { crate::domain::Range::ANY }
250    fn number(&self, x: F) -> Self::V { crate::domain::Range::of(Some(x.wide())) }
251    fn constant(&self, x: Self::V) -> Option<F> { x.constant().map(F::narrow) }
252    fn unary(&self, op: Unary, x: Self::V) -> Self::V { if op == Unary::Magnitude { x.magnitude() } else { crate::domain::Range::ANY } }
253    fn binary(&self, _: Binary, _: Self::V, _: Self::V) -> Self::V { crate::domain::Range::ANY }
254    fn compare(&self, op: Cmp, x: Self::V, y: Self::V) -> Self::B { Self::compare(op, x, y) }
255    fn multiply_add(&self, _: Self::V, _: Self::V, _: Self::V) -> Self::V { crate::domain::Range::ANY }
256    /// No overflow (`Some(false)`) where the operation's range, as its formula's facts give it, lies within the computing dtype's values. Past 2⁵³ a bound and the result's ends round, so a result stays strictly inside such a bound.
257    fn overflow(&self, op: Binary, x: Self::V, y: Self::V) -> Self::B {
258        use crate::facts::Formula;
259        let result = match op {
260            Binary::Add => crate::Add.range([x, y]), Binary::Subtract => crate::Subtract.range([x, y]), Binary::Multiply => crate::Multiply.range([x, y]),
261            _ => return None
262        };
263        let fits = crate::domain::Range::of_dtype(self.0);
264        let exact = |bound: f64| bound.abs() < 2f64.powi(53);
265        let above = if exact(fits.lo) { result.lo >= fits.lo } else { result.lo > fits.lo };
266        let below = if exact(fits.hi) { result.hi <= fits.hi } else { result.hi < fits.hi };
267        (above && below).then_some(false)
268    }
269    fn and(&self, x: Self::B, y: Self::B) -> Self::B {
270        match (x, y) { (Some(false), _) | (_, Some(false)) => Some(false), (Some(true), Some(true)) => Some(true), _ => None }
271    }
272    fn or(&self, x: Self::B, y: Self::B) -> Self::B {
273        match (x, y) { (Some(true), _) | (_, Some(true)) => Some(true), (Some(false), Some(false)) => Some(false), _ => None }
274    }
275    fn not(&self, x: Self::B) -> Self::B { x.map(|x| !x) }
276    fn select(&self, test: Self::B, x: Self::V, y: Self::V) -> Self::V { Self::selected(test, x, y) }
277}
278
279/// Scalar instantiation of the same formulae used by slice kernels and graph lowering.
280#[derive(Clone, Copy, Debug, Default)]
281pub struct NativeOps<const FUSED: bool> { pub tolerance: Option<f64> }
282impl<const FUSED: bool> NativeOps<FUSED> { pub const DEFAULT: Self = Self { tolerance: None }; }
283pub type Floats = NativeOps<{ crate::FUSED }>;
284#[allow(non_upper_case_globals)]
285pub const Floats: Floats = NativeOps::DEFAULT;
286
287impl<F: Float, const FUSED: bool> Ops<F> for NativeOps<FUSED> {
288    type V = F;
289    type B = bool;
290    type I = u64;
291    type W = f64;
292    #[inline(always)]
293    fn fused(&self) -> bool { FUSED }
294    #[inline(always)]
295    fn widen(&self, x: F) -> f64 { x.wide() }
296    #[inline(always)]
297    fn wide_number(&self, x: f64) -> f64 { x }
298    #[inline(always)]
299    fn wide_multiply_add(&self, x: f64, y: f64, z: f64) -> f64 { if FUSED { x.mul_add(y, z) } else { x * y + z } }
300    #[inline(always)]
301    fn narrow(&self, x: f64) -> F { F::narrow(x) }
302    #[inline(always)]
303    fn width(&self) -> crate::buffer::FloatWidth { if F::Working::WIDE { crate::buffer::FloatWidth::F64 } else { crate::buffer::FloatWidth::F32 } }
304    u64_integers!(64);
305    #[inline(always)]
306    fn to_integer(&self, x: F) -> u64 { (x.wide() as i64) as u64 }
307    #[inline(always)]
308    fn from_integer(&self, x: u64) -> F { F::narrow((x as i64) as f64) }
309    #[inline(always)]
310    fn to_bits(&self, x: F) -> u64 { if F::Working::WIDE { x.wide().to_bits() } else { (x.wide() as f32).to_bits() as u64 } }
311    #[inline(always)]
312    fn from_bits(&self, x: u64) -> F { F::narrow(if F::Working::WIDE { f64::from_bits(x) } else { f32::from_bits(x as u32) as f64 }) }
313    #[inline(always)]
314    fn number(&self, x: F) -> F { x }
315    #[inline(always)]
316    fn unary(&self, op: Unary, x: F) -> F {
317        match op {
318            Unary::Negate => -x, Unary::Magnitude => x.abs(), Unary::Sqrt => x.sqrt(), Unary::Floor => x.floor(), Unary::Ln => crate::ln(x),
319            Unary::Sin => crate::sin(x), Unary::Cos => crate::cos(x), Unary::Sinh => crate::sinh(x), Unary::Cosh => crate::cosh(x),
320            Unary::Sqrt1mx2 => crate::sqrt1mx2(x), Unary::Sqrt1px2 => crate::sqrt1px2(x), Unary::Digamma => crate::digamma(x), Unary::Exp => crate::exp(x),
321            Unary::Lgamma => crate::lgamma(x), Unary::Not => F::one() - x
322        }
323    }
324    #[inline(always)]
325    fn binary(&self, op: Binary, x: F, y: F) -> F {
326        match op {
327            Binary::Add => x + y, Binary::Subtract => x - y, Binary::Multiply => x * y, Binary::Divide => x / y, Binary::Larger => { if x > y { x } else { y } }
328            Binary::Smaller => { if x < y { x } else { y } } Binary::Binom => crate::binom(x, y), Binary::BinomDn => crate::binom_dn(x, y),
329            Binary::Power => F::from_working(crate::pow::pow::<F::Working, FUSED>(x.working(), y.working())), Binary::BinomDk => crate::binom_dk(x, y),
330            Binary::Polygamma => crate::polygamma(x, y),
331            Binary::BitAnd => unreachable!("floats take no bitwise and: a recipe asks for one only where `Ops::integral` holds")
332        }
333    }
334    plain_logic!(F);
335    #[inline(always)]
336    fn multiply_add(&self, x: F, y: F, z: F) -> F { F::from_working(x.working().mla::<FUSED>(y.working(), z.working())) }
337    #[inline(always)]
338    fn overflow(&self, _op: Binary, _x: F, _y: F) -> bool { false }
339    #[inline(always)]
340    fn constant(&self, value: F) -> Option<F> { Some(value) }
341    #[inline(always)]
342    fn tolerance(&self, fallback: F) -> F { self.tolerance.map_or(fallback, F::narrow) }
343}
344
345/// Whether `x` and `y` lie within tolerance `t` of each other: `|x-y| < t·max(|x|, |y|)`. With a strict `<`, an infinity is close to no number without a finiteness test, because `inf < inf` is false. NaN is close to nothing.
346#[inline(always)]
347pub fn close<F: Float, O: Ops<F> + ?Sized>(o: &O, t: O::V, x: O::V, y: O::V) -> O::B {
348    let gap = o.subtract(x, y);
349    let gap = o.magnitude(gap);
350    let (a, b) = (o.magnitude(x), o.magnitude(y));
351    let size = o.larger(a, b);
352    let bound = o.multiply(t, size);
353    o.less(gap, bound)
354}
355
356/// Whether `x` equals `y`: `x == y`, or `close`, which gives whether they're close. Each of these compositions runs `close` after its plain comparison: in the kernels' loops, that order is what lets LLVM vectorize 16 items at a time rather than 8.
357#[inline(always)]
358pub fn equal<F: Float, O: Ops<F> + ?Sized>(o: &O, x: O::V, y: O::V, close: impl FnOnce(&O) -> O::B) -> O::B {
359    if o.integral() { return o.equal(x, y); }
360    let same = o.equal(x, y);
361    let close = close(o);
362    o.or(same, close)
363}
364
365/// Whether `x` is below `y` and doesn't equal it, where `close` gives whether they're close. `x < y` already implies `x != y`, so only `close` is tested.
366#[inline(always)]
367pub fn less<F: Float, O: Ops<F> + ?Sized>(o: &O, x: O::V, y: O::V, close: impl FnOnce(&O) -> O::B) -> O::B {
368    if o.integral() { return o.less(x, y); }
369    let below = o.less(x, y);
370    let close = close(o);
371    let apart = o.not(close);
372    o.and(below, apart)
373}
374
375/// Whether `x` is below `y` or equals it, where `close` gives whether they're close. `x <= y` already holds where `x == y`, so only `close` is added.
376#[inline(always)]
377pub fn less_equal<F: Float, O: Ops<F> + ?Sized>(o: &O, x: O::V, y: O::V, close: impl FnOnce(&O) -> O::B) -> O::B {
378    if o.integral() { return o.less_equal(x, y); }
379    let below = o.less_equal(x, y);
380    let close = close(o);
381    o.or(below, close)
382}
383
384/// The floor of `y` within tolerance `t`. `y` equals the whole number above it when raising `y` by `t·|y|` reaches that number. Capping the raise just below a half keeps a whole number from reaching the next one where `t·|y|` exceeds a half. Where floats are 1 or more apart, the sum rounds back to `y`. A zero result is `0`, since `-0` plus a zero raise is `0`.
385#[inline(always)]
386pub fn floor<F: Float, O: Ops<F> + ?Sized>(o: &O, t: O::V, y: O::V) -> O::V {
387    if o.integral() { return y; }
388    let size = o.magnitude(y);
389    let raise = o.multiply(t, size);
390    let most = o.number(F::narrow(0.5).next_down());
391    let raise = o.smaller(raise, most);
392    let raised = o.add(y, raise);
393    o.floor(raised)
394}
395
396#[inline(always)]
397pub(crate) fn integer_floor_divide<F: Float, O: Ops<F> + ?Sized>(o: &O, x: O::V, y: O::V) -> O::V {
398    let q = o.divide(x, y);
399    let r = o.subtract(x, o.multiply(q, y));
400    let zero = o.number(F::zero());
401    let lower = o.or(o.and(o.less(r, zero), o.less(zero, y)), o.and(o.less(zero, r), o.less(y, zero)));
402    o.subtract(q, o.one_where(lower))
403}
404
405/// The exact floor of `y`. Adding 0 turns IEEE's `-0` into `0`.
406#[inline(always)]
407pub fn exact_floor<F: Float, O: Ops<F> + ?Sized>(o: &O, y: O::V) -> O::V {
408    let whole = o.floor(y);
409    let zero = o.number(F::zero());
410    o.add(whole, zero)
411}
412
413/// The ceiling of `y` through `floor`, the floor at the same tolerance: `0 - floor(-y)`.
414#[inline(always)]
415pub fn ceil<F: Float, O: Ops<F> + ?Sized>(o: &O, y: O::V, floor: impl FnOnce(&O, O::V) -> O::V) -> O::V {
416    if o.integral() { return y; }
417    let negated = o.negate(y);
418    let whole = floor(o, negated);
419    let zero = o.number(F::zero());
420    o.subtract(zero, whole)
421}
422
423/// `y` modulo `x`: `y - x·q`, for `q` the floor of `y÷x` that `floor` gives, rounded once where the target fuses multiply-add. Within a tolerance `t`, a product `x·q` that equals `y` leaves no remainder. `x` = 0 gives `y`. At no tolerance, where `y÷x` passes the float's precision, the rounded quotient has lost its low digits: `tolerant::Residue` recomputes those items.
424#[inline(always)]
425pub fn residue<F: Float, O: Ops<F> + ?Sized>(o: &O, t: Option<O::V>, x: O::V, y: O::V, floor: impl FnOnce(&O, O::V) -> O::V) -> O::V {
426    let quotient = o.divide(y, x);
427    let q = floor(o, quotient);
428    let minus = o.negate(x);
429    let r = o.multiply_add(minus, q, y);
430    let r = match t {
431        Some(t) => {
432            let product = o.multiply(x, q);
433            let whole = equal(o, product, y, |o| close(o, t, product, y));
434            let zero = o.number(F::zero());
435            o.select(whole, zero, r)
436        }
437        None => r,
438    };
439    let zero = o.number(F::zero());
440    let none = o.equal(x, zero);
441    o.select(none, y, r)
442}
443
444/// The square root of `y`, with the values that `pow` gives `y^0.5` at -0 and -∞: 0 and ∞.
445#[inline(always)]
446pub fn root2<F: Float, O: Ops<F> + ?Sized>(o: &O, y: O::V) -> O::V {
447    let root = o.sqrt(y);
448    let zero = o.number(F::zero());
449    let root = o.add(root, zero);
450    let low = o.number(F::neg_infinity());
451    let infinite = o.equal(y, low);
452    let flipped = o.negate(y);
453    o.select(infinite, flipped, root)
454}
455
456/// The real part of √y: √y where y is positive, 0 at or below 0, and NaN at NaN, which `larger` keeps.
457#[inline(always)]
458pub fn real_sqrt<F: Float, O: Ops<F> + ?Sized>(o: &O, y: O::V) -> O::V {
459    let zero = o.number(F::zero());
460    let clamped = o.larger(zero, y);
461    o.sqrt(clamped)
462}
463
464/// (sin πx, cos πx), exact at whole and half x: x = n/2 + r with n the whole number nearest 2x and |r| up to ¼, where n mod 4 picks the quadrant.
465#[inline(always)]
466pub fn sincospi<F: Float, O: Ops<F> + ?Sized>(o: &O, x: O::V) -> (O::V, O::V) {
467    let (half, quarter) = (o.number(F::narrow(0.5)), o.number(F::narrow(0.25)));
468    let n = o.floor(o.add(o.add(x, x), half));
469    let a = o.multiply(o.subtract(x, o.multiply(n, half)), o.number(F::narrow(core::f64::consts::PI)));
470    let (s, k) = (o.sin(a), o.cos(a));
471    let q = o.subtract(n, o.multiply(o.number(F::narrow(4.0)), o.floor(o.multiply(n, quarter))));
472    let [q0, q1, q2] = [0.0, 1.0, 2.0].map(|m| o.equal(q, o.number(F::narrow(m))));
473    // 0 - v negates v exactly, and gives +0, never ¯0, at an exact half step.
474    let zero = o.number(F::zero());
475    let (ns, nk) = (o.subtract(zero, s), o.subtract(zero, k));
476    (o.select(q0, s, o.select(q1, k, o.select(q2, ns, nk))), o.select(q0, k, o.select(q1, ns, o.select(q2, nk, s))))
477}
478
479/// Whether `v` lies in `band` against the range `lo` to `hi` of floats equal to one number (`Tolerant::range`), by one or two plain comparisons.
480#[inline(always)]
481pub fn in_band<F: Float, O: Ops<F> + ?Sized>(o: &O, band: Band, lo: O::V, hi: O::V, v: O::V) -> O::B {
482    match band {
483        Band::Inside | Band::Outside => {
484            let (above, below) = (o.less_equal(lo, v), o.less_equal(v, hi));
485            let inside = o.and(above, below);
486            if matches!(band, Band::Outside) { o.not(inside) } else { inside }
487        }
488        Band::Below => o.less(v, lo),
489        Band::NotAbove => o.less_equal(v, hi),
490        Band::Above => o.less(hi, v),
491        Band::NotBelow => o.less_equal(lo, v),
492    }
493}
494
495/// `1 ÷ x`.
496#[inline(always)]
497pub fn reciprocal<F: Float, O: Ops<F> + ?Sized>(o: &O, x: O::V) -> O::V { let one = o.number(F::one()); o.divide(one, x) }
498
499/// `π × x`, with π at `F`'s width.
500#[inline(always)]
501pub fn pi_times<F: Float, O: Ops<F> + ?Sized>(o: &O, x: O::V) -> O::V { let pi = o.number(F::narrow(std::f64::consts::PI)); o.multiply(x, pi) }
502
503/// `π × (x ÷ y)`, an angle of `x` over `y` turns of half a circle.
504#[inline(always)]
505pub fn pi_ratio<F: Float, O: Ops<F> + ?Sized>(o: &O, x: O::V, y: O::V) -> O::V { let ratio = o.divide(x, y); pi_times(o, ratio) }
506
507/// `x ÷ y`, or `x` where both are zero, as Mathish division gives `0÷0`.
508#[inline(always)]
509pub fn mathish_divide<F: Float, O: Ops<F> + ?Sized>(o: &O, x: O::V, y: O::V) -> O::V {
510    let quotient = o.divide(x, y);
511    let zero = o.number(F::zero());
512    let (a, b) = (o.equal(x, zero), o.equal(y, zero));
513    let both = o.and(a, b);
514    o.select(both, x, quotient)
515}
516
517/// The sign of `x`: `-1`, `1`, or `x` itself where `x` is a zero or NaN.
518#[inline(always)]
519pub fn sign<F: Float, O: Ops<F> + ?Sized>(o: &O, x: O::V) -> O::V {
520    let (zero, one, minus) = (o.number(F::zero()), o.number(F::one()), o.number(-F::one()));
521    let (above, below) = (o.less(zero, x), o.less(x, zero));
522    o.select(above, one, o.select(below, minus, x))
523}
524
525/// `x` raised to an exponent admitted by `e`. Large and negative powers use [`whole`]; small positive powers use [`multiplied`]. Halves first take `root2`. `None` for `Exponent::General`.
526#[inline(always)]
527pub fn power<F: Float, O: Ops<F> + ?Sized>(o: &O, e: Exponent, x: O::V) -> Option<O::V> {
528    let (m, negative, x) = match e {
529        Exponent::Whole { m, negative } => (m, negative, x), Exponent::Halves { m, negative } => (m, negative, root2(o, x)), Exponent::General => return None
530    };
531    Some(if !o.integral() && (m > crate::pow::MULTIPLIED || (negative && m > 1)) { whole(o, m, negative, x) } else { multiplied(o, m, negative, x) })
532}
533
534/// Positive powers up to `MULTIPLIED`, squaring from m's lowest set bit, and the reciprocal for m = 1. A rounding at the step that gives x^k carries into x^m about m/k times. A result that the float holds exactly comes out exactly.
535#[inline(always)]
536fn multiplied<F: Float, O: Ops<F> + ?Sized>(o: &O, m: u32, negative: bool, x: O::V) -> O::V {
537    let (mut base, mut k) = (x, m);
538    while k & 1 == 0 {
539        base = o.multiply(base, base);
540        k >>= 1;
541    }
542    let mut r = base;
543    while k > 1 {
544        k >>= 1;
545        base = o.multiply(base, base);
546        if k & 1 == 1 { r = o.multiply(r, base); }
547    }
548    if negative { reciprocal(o, r) } else { r }
549}
550
551/// A power that [`whole`] has part computed: `h + l` in double words, or `h` alone where `l` is `None`.
552pub(crate) type Word<V> = (V, Option<V>);
553
554/// Powers admitted for fused multiply-add. Negative powers start with the reciprocal and its rounding error, avoiding an overflowing positive power whose reciprocal is still representable. Raises [`base`] by squaring and multiplying from m's highest set bit, with a [`step`] for each lower bit. A result that the float holds exactly comes out exactly.
555#[inline(always)]
556pub(crate) fn whole<F: Float, O: Ops<F> + ?Sized>(o: &O, m: u32, negative: bool, x: O::V) -> O::V {
557    let (b, mut error) = base(o, negative, x);
558    let mut w = (b, None);
559    for i in (0..u32::BITS - 1 - m.leading_zeros()).rev() { w = step(o, w, b, m, i, &mut error); }
560    w.0
561}
562
563/// The number that [`whole`] raises to m: x, or q = 1/x with its relative error ε = 1 - x·q where `negative`, since 1/x is q·(1 + ε) to within ε².
564#[inline(always)]
565pub(crate) fn base<F: Float, O: Ops<F> + ?Sized>(o: &O, negative: bool, x: O::V) -> (O::V, Option<O::V>) {
566    if !negative { return (x, None); }
567    let q = reciprocal(o, x);
568    let one = o.number(F::one());
569    (q, Some(o.multiply_add(o.negate(x), q, one)))
570}
571
572/// The steps of bit position `i` of [`whole`]'s m: `w` squared, then times the base `b` where that bit is set.
573#[inline(always)]
574pub(crate) fn step<F: Float, O: Ops<F> + ?Sized>(o: &O, w: Word<O::V>, b: O::V, m: u32, i: u32, error: &mut Option<O::V>) -> Word<O::V> {
575    let w = times(o, w, None, m, i, error);
576    if (m >> i) & 1 == 1 { times(o, w, Some(b), m, i, error) } else { w }
577}
578
579/// One step of [`whole`] at bit position `i`: `w` squared, or times `b` for `by` = `Some(b)`. A rounding at the step that gives x^k carries into x^m about m/k times. Each step at a position above 0 runs in double words, taking its product's rounding error exactly from a fused multiply-add. The first step after them rounds the double word to one word. That step also adds the base's error `error`, times m and halved once for each square from that step on. This turns a power of q into a power of 1/x. The steps after it run in single words. The roundings left keep x^m within 4 ulps.
580#[inline(always)]
581fn times<F: Float, O: Ops<F> + ?Sized>(o: &O, (h, l): Word<O::V>, by: Option<O::V>, m: u32, i: u32, error: &mut Option<O::V>) -> Word<O::V> {
582    let y = by.unwrap_or(h);
583    // The coefficient of l in the product: 2h for a square, b for a product with b.
584    let twice = |o: &O| by.unwrap_or_else(|| o.add(h, h));
585    if i > 0 {
586        let p = o.multiply(h, y);
587        let e = o.multiply_add(h, y, o.negate(p));
588        return (p, Some(match l { Some(l) => o.multiply_add(twice(o), l, e), None => e }));
589    }
590    let squares = i + by.is_none() as u32;
591    let fold = error.take().map(|e| o.multiply(e, o.number(F::narrow(m as f64 / (1u64 << squares) as f64))));
592    let l = match (l, fold) {
593        (None, None) => return (o.multiply(h, y), None), (Some(l), None) => l, (None, Some(c)) => o.multiply(h, c),
594        (Some(l), Some(c)) => o.multiply_add(h, c, l)
595    };
596    let k = o.multiply(twice(o), l);
597    // k is infinite or NaN only where h is, or where h·y overflows. h·y alone is the power there.
598    let k = o.select(finite(o, k), k, o.number(F::zero()));
599    (o.multiply_add(h, y, k), None)
600}
601
602/// Whether `v` is finite: `v - v` is 0 there, and NaN at an infinity or NaN.
603#[inline(always)]
604fn finite<F: Float, O: Ops<F> + ?Sized>(o: &O, v: O::V) -> O::B { o.equal(o.subtract(v, v), o.number(F::zero())) }
605
606#[inline(always)]
607pub fn horner<F: Float, O: Ops<F> + ?Sized>(o: &O, x: O::V, coefficients: &[f64]) -> O::V {
608    let mut result = o.number(F::narrow(coefficients[0]));
609    for &coefficient in &coefficients[1..] { result = o.multiply_add(result, x, o.number(F::narrow(coefficient))); }
610    result
611}
612
613#[inline(always)]
614pub fn ln2<F: Float, O: Ops<F> + ?Sized>(o: &O) -> (O::V, O::V) {
615    let (hi, lo) = if o.width() == crate::buffer::FloatWidth::F64 {
616        (0.69314718055966295651160180568695068359375, 0.28235290563031577122588448175013436025525412068e-12)
617    } else { (0.693145751953125, 1.428606765330187e-06) };
618    (o.number(F::narrow(hi)), o.number(F::narrow(lo)))
619}
620
621#[inline(always)]
622pub fn rounding_shift<F: Float, O: Ops<F> + ?Sized>(o: &O) -> O::V {
623    o.number(F::narrow(if o.width() == crate::buffer::FloatWidth::F64 { 6755399441055744.0 } else { 12582912.0 }))
624}
625
626/// 2^n from z = rounding_shift + n, where n lies within the normal exponent range.
627#[inline(always)]
628pub fn pow2<F: Float, O: Ops<F> + ?Sized>(o: &O, z: O::V) -> O::V {
629    let (mantissa, bias) = if o.width() == crate::buffer::FloatWidth::F64 { (52, 1023) } else { (23, 127) };
630    let bits = o.integer_op(Integer::Add, o.to_bits(z), o.integer(bias));
631    o.from_bits(o.integer_op(Integer::ShiftLeft, bits, o.integer(mantissa)))
632}
633
634/// y times 2^n, with one rounding when the result is subnormal.
635#[inline(always)]
636pub fn ldexp<F: Float, O: Ops<F> + ?Sized>(o: &O, y: O::V, n: O::V) -> O::V {
637    let shift = rounding_shift(o);
638    let half = o.multiply_add(n, o.number(F::narrow(0.5)), shift);
639    let rest = o.add(o.subtract(n, o.subtract(half, shift)), shift);
640    o.multiply(o.multiply(y, pow2(o, half)), pow2(o, rest))
641}
642
643/// Positive normal x = m times 2^e, with m in [0.75, 1.5].
644#[inline(always)]
645pub fn split<F: Float, O: Ops<F> + ?Sized>(o: &O, x: O::V) -> (O::V, O::V) {
646    let (mantissa, bias) = if o.width() == crate::buffer::FloatWidth::F64 { (52, 1023) } else { (23, 127) };
647    let scaled = o.multiply(x, o.number(F::narrow(1.0 / 0.75)));
648    let exponent = o.integer_op(Integer::ShiftRight, o.to_bits(scaled), o.integer(mantissa));
649    let exponent = o.integer_op(Integer::And, exponent, o.integer(2 * bias + 1));
650    let unbiased = o.integer_op(Integer::Subtract, exponent, o.integer(bias));
651    let offset = o.integer_op(Integer::ShiftLeft, unbiased, o.integer(mantissa));
652    (o.from_bits(o.integer_op(Integer::Subtract, o.to_bits(x), offset)), o.from_integer(unbiased))
653}
654
655/// Positive x = m times 2^e, including subnormals.
656#[inline(always)]
657pub fn frexp<F: Float, O: Ops<F> + ?Sized>(o: &O, x: O::V) -> (O::V, O::V) {
658    let smallest = if o.width() == crate::buffer::FloatWidth::F64 { f64::MIN_POSITIVE } else { f32::MIN_POSITIVE as f64 };
659    let small = o.less(x, o.number(F::narrow(smallest)));
660    let scale = o.select(small, o.number(F::narrow(18446744073709551616.0)), o.number(F::one()));
661    let (m, e) = split(o, o.multiply(x, scale));
662    (m, o.subtract(e, o.select(small, o.number(F::narrow(64.0)), o.number(F::zero()))))
663}