Skip to main content

rustymath_core/
tolerant.rs

1//! Tolerant comparison of floats: `x` equals `y` within a relative tolerance `t` when `|x-y|` is less than `t` times the larger of `|x|` and `|y|`. APL's definition says at most, which differs only where `|x-y|` lands exactly on that bound. Dyalog APL calls `t` the comparison tolerance, `⎕CT`, and its default is 1e-14. A tolerance has no effect when either number is zero.
2//!
3//! A tolerance is a value of a type that implements `Tolerant`: a `Tolerance`, or `Exactly`, whose methods compile to plain IEEE comparisons and `floor`. A function written once over `Tolerant` compiles to a version for each. `with_tolerance!` picks one when the program runs, outside any loop. The methods are generic over the float type and compute at its width. `TolerantComplex` gives the complex versions. The function objects `Equal`, `NotEqual`, `Less`, `LessEqual`, `Greater`, `GreaterEqual`, `Matches`, `Compare`, `Floor`, `Ceil`, `Residue`, `FloorDivide`, `Gcd` and `Lcm` each hold a `Tolerance` and give the same results as the methods, one item at a time or over slices. A slice form picks its loop with `with_tolerance!` once. A comparison's slice form compares many items with one number through the range of floats equal to it (`Tolerant::range`), so each item takes one or two plain comparisons. All but `Matches` and `Compare` also have integer forms, from the `integer` module, which ignore the tolerance. `Floor` and `Ceil` also take Booleans.
4//!
5//! Tolerant equality isn't transitive: `x` can equal `y` and `y` equal `z` while `x` doesn't equal `z`. Hash maps and sorting need a transitive `PartialEq`. This module therefore has no float type with a tolerant `==`. Each comparison is a method of the tolerance.
6use crate::{
7    Arg, BETWEEN_CHECKS, buffer::Element, convert::{float, int}, error::Error, float::{Float, Native}, interrupt::check, recipe::{self, Floats},
8    search::{self, Chains, Key, Links}, slice::{Total, pairs_into}
9};
10use num_bigint::BigInt;
11use num_complex::Complex;
12use num_rational::BigRational;
13use num_traits::{Signed, ToPrimitive, Zero};
14use std::{cmp::Ordering, marker::PhantomData};
15
16/// A relative tolerance, from 0 up to but not including 1. A tolerance of 0 compares exactly, as `Exactly` does, but with more work.
17#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
18pub struct Tolerance(f64);
19
20impl Tolerance {
21    /// Exact comparison.
22    pub const EXACT: Self = Self(0.0);
23    /// Dyalog APL's default comparison tolerance, 1e-14. It is about 45 units in the last place of an `f64`.
24    pub const DEFAULT: Self = Self(1e-14);
25    /// The tolerance `t`, when it lies from 0 up to but not including 1. `None` for any other `t`, including NaN.
26    #[inline]
27    pub fn new(t: f64) -> Option<Self> { (0.0..1.0).contains(&t).then_some(Self(t)) }
28    /// Whether this tolerance is 0.
29    #[inline]
30    pub fn is_exact(self) -> bool { self.0 == 0.0 }
31    /// The tolerance, as a relative difference.
32    #[inline]
33    pub const fn value(self) -> f64 { self.0 }
34}
35
36/// Comparison within a tolerance. `Exactly` and `Tolerance` implement it.
37pub trait Tolerant: Copy + Send + Sync {
38    /// The tolerance: 0 for `Exactly`.
39    fn value(self) -> f64;
40    /// Whether `x` and `y` lie within the tolerance of each other: `|x-y|` is less than the tolerance times the larger of `|x|` and `|y|`. NaN and the infinities are close to nothing. `less` and `less_equal` read it without `equal`'s `x == y`, because `x < y` already implies `x != y`, and `x <= y` already holds where `x == y`.
41    fn close<F: Float>(self, x: F, y: F) -> bool;
42    /// Whether `x` equals `y`: it is `y`, or `close` to it. NaN equals nothing, as in IEEE, and an infinity equals only itself.
43    #[inline]
44    fn equal<F: Float>(self, x: F, y: F) -> bool { recipe::equal::<F, _>(&Floats, x, y, |_| self.close(x, y)) }
45    /// The floor of `y`: the whole number nearest to `y`, unless it lies above `y` and doesn't equal it. Then the whole number below. A floor or ceiling that is zero is `0`, never `-0`, as BPL's numbers give it.
46    #[inline]
47    fn floor<F: Float>(self, y: F) -> F { self.floor_in::<F, Floats>(&Floats, y) }
48    /// `floor` through `o`: the formula that the `Floor`, `Ceil` and `Residue` objects give compiled code (`Facts::formula`).
49    fn floor_in<F: Float, O: recipe::Ops<F> + ?Sized>(self, o: &O, y: O::V) -> O::V;
50    /// The ceiling of `y`: the whole number nearest to `y`, unless it lies below `y` and doesn't equal it. Then the whole number above.
51    #[inline]
52    fn ceil<F: Float>(self, y: F) -> F { self.ceil_in::<F, Floats>(&Floats, y) }
53    /// `ceil` through `o`.
54    #[inline]
55    fn ceil_in<F: Float, O: recipe::Ops<F> + ?Sized>(self, o: &O, y: O::V) -> O::V { recipe::ceil(o, y, |o, v| self.floor_in(o, v)) }
56    /// Whether `x` and `y` match: they are equal, or both are NaN. A search that must find NaN compares with `matches`.
57    #[inline]
58    fn matches<F: Float>(self, x: F, y: F) -> bool { self.equal(x, y) | (x.is_nan() & y.is_nan()) }
59    /// Whether `x` is below `y` and doesn't equal it. Comparisons with NaN are false.
60    #[inline]
61    fn less<F: Float>(self, x: F, y: F) -> bool { recipe::less::<F, _>(&Floats, x, y, |_| self.close(x, y)) }
62    /// Whether `x` is below `y` or equals it. Comparisons with NaN are false.
63    #[inline]
64    fn less_equal<F: Float>(self, x: F, y: F) -> bool { recipe::less_equal::<F, _>(&Floats, x, y, |_| self.close(x, y)) }
65    /// Whether `x` is above `y` and doesn't equal it. Comparisons with NaN are false.
66    #[inline]
67    fn greater<F: Float>(self, x: F, y: F) -> bool { self.less(y, x) }
68    /// Whether `x` is above `y` or equals it. Comparisons with NaN are false.
69    #[inline]
70    fn greater_equal<F: Float>(self, x: F, y: F) -> bool { self.less_equal(y, x) }
71    /// The order of `x` and `y`, which is `Equal` when they are equal. `None` when either is NaN.
72    #[inline]
73    fn compare<F: Float>(self, x: F, y: F) -> Option<Ordering> { if self.equal(x, y) { Some(Ordering::Equal) } else { x.partial_cmp(&y) } }
74    /// `y` modulo `x`, through `recipe::residue`: `y - x·floor(y/x)` from this tolerance's floor of the quotient, rounded once where the target fuses multiply-add. At nonzero tolerance, a product equal to `y` within tolerance leaves no remainder. `x` = 0 gives `y`.
75    #[inline]
76    fn residue<F: Float>(self, x: F, y: F) -> F { self.residue_in::<F, Floats>(&Floats, x, y) }
77    /// `residue` through `o`.
78    #[inline]
79    fn residue_in<F: Float, O: recipe::Ops<F> + ?Sized>(self, o: &O, x: O::V, y: O::V) -> O::V {
80        let tolerance = (!exact(self, o)).then(|| o.tolerance(F::narrow(self.value())));
81        recipe::residue(o, tolerance, x, y, |o, v| self.floor_in(o, v))
82    }
83    /// The whole number nearest `y`, and whether `y` equals it within this tolerance.
84    #[inline]
85    fn nearest_whole<F: Float>(self, y: F) -> (F, bool) { self.nearest_whole_in(&Floats, y) }
86    #[inline]
87    fn nearest_whole_in<F: Float, O: recipe::Ops<F> + ?Sized>(self, o: &O, y: O::V) -> (O::V, O::B) {
88        let below = o.floor(y);
89        let n = o.select(o.less(o.number(F::narrow(0.5)), o.subtract(y, below)), o.add(below, o.number(F::one())), below);
90        let scale = o.multiply(o.number(F::narrow(self.value())), o.larger(o.magnitude(y), o.magnitude(n)));
91        (n, o.less_equal(o.magnitude(o.subtract(y, n)), scale))
92    }
93    /// The whole number equal to `y`, when there is one.
94    #[inline]
95    fn whole<F: Float>(self, y: F) -> Option<F> { let (n, equal) = self.nearest_whole(y); equal.then_some(n) }
96    /// The floats equal to `c`, as `(lo, hi)`: `equal(x, c)` holds exactly when `lo <= x && x <= hi`. Comparing many floats with `c` then takes two plain comparisons for each, in a loop that vectorizes. `None` when `c` is NaN, which equals nothing.
97    fn range<F: Float>(self, c: F) -> Option<(F, F)> {
98        if c.is_nan() { return None; }
99        if !c.is_finite() || c == F::zero() { return Some((c, c)); }
100        let t = F::narrow(self.value());
101        let (near, far) = (c * (F::one() - t), c / (F::one() - t));
102        let (lo, hi) = if c > F::zero() { (near, far) } else { (far, near) };
103        // The last float equal to `c` in the direction that `out` steps, found from the estimate `x`. `back` steps towards `c`.
104        let end = |mut x: F, out: fn(F) -> F, back: fn(F) -> F| {
105            while !self.equal(x, c) { x = back(x); }
106            while self.equal(out(x), c) { x = out(x); }
107            x
108        };
109        Some((end(lo, F::next_down, F::next_up), end(hi, F::next_up, F::next_down)))
110    }
111    /// The greatest common divisor of `x` and `y`: the larger magnitude divided by the numerator of the first convergent of the ratio of the magnitudes that equals the ratio. It computes in `f64` and rounds. NaN when either argument is NaN or infinite. Whole magnitudes whose product is below 1e14 take integer gcd, which gives the same result at any tolerance up to the default.
112    fn gcd<F: Float>(self, x: F, y: F) -> F {
113        let (x, y) = (x.wide(), y.wide());
114        if !(x.is_finite() && y.is_finite()) { return F::nan(); }
115        let (x, y) = (x.abs().max(y.abs()), x.abs().min(y.abs()));
116        if y == 0.0 { return F::narrow(x); }
117        let whole = x.fract() == 0.0 && y.fract() == 0.0 && x * y < 1e14 && self.value() <= Tolerance::DEFAULT.value();
118        if let Some(g) = whole.then(|| crate::integer::gcd(int(x)?, int(y)?)).flatten() { return float(g); }
119        let (t, (nx, dy)) = (self.value(), ratio_parts(x, y));
120        if t == 0.0 {
121            // Odd significands share no powers of two; the smaller exponent supplies their common unit.
122            let g = crate::integer::gcd(nx.0 as i64, dy.0 as i64).unwrap() as f64;
123            let unit = if nx.1 == 0 { x / nx.0 as f64 } else { y / dy.0 as f64 };
124            return F::narrow(unit * g);
125        }
126        if let Some(g) = fixed_gcd(x, y, nx, dy, t) { return F::narrow(g); }
127        // The same walk on big integers, where the test `|p·d − q·n| ≤ t·n·q` is exact for `t = m·2^e`.
128        let (n, d) = (BigInt::from(nx.0) << nx.1, BigInt::from(dy.0) << dy.1);
129        let (m, e, _) = num_traits::Float::integer_decode(t);
130        let p = convergents(n.clone(), d.clone(), |p, q| {
131            let (gap, bound) = ((p * &d - q * &n).abs(), BigInt::from(m) * &n * q);
132            let within = gap.is_zero() || m != 0 && if e >= 0 { gap <= bound << e as usize } else { gap << (-e) as usize <= bound };
133            within.then(|| p.clone())
134        });
135        F::narrow((BigRational::from_float(x).unwrap() / BigRational::from_integer(p)).to_f64().unwrap())
136    }
137}
138
139/// `x / y`, for finite positive `x` and `y`, as `n / d` with `n = mx·2^sx` and `d = my·2^sy`: odd mantissas, with the larger exponent's excess as a shift on its side.
140fn ratio_parts(x: f64, y: f64) -> ((u64, u32), (u64, u32)) {
141    let odd = |v: f64| {
142        let (m, e, _) = num_traits::Float::integer_decode(v);
143        (m >> m.trailing_zeros(), i32::from(e) + m.trailing_zeros() as i32)
144    };
145    let ((mx, ex), (my, ey)) = (odd(x), odd(y));
146    if ex >= ey { ((mx, (ex - ey) as u32), (my, 0)) } else { ((mx, 0), (my, (ey - ex) as u32)) }
147}
148
149/// Walks the convergents `p/q` of the continued fraction of `n/d`, for `n, d > 0`, and gives `done`'s answer for the first convergent where it gives one. The last convergent is `n/d` itself, so `done` must answer there at the latest.
150fn convergents<I: num_traits::Num + Clone, R>(n: I, d: I, mut done: impl FnMut(&I, &I) -> Option<R>) -> R {
151    let (mut a, mut b) = (n, d);
152    let (mut p0, mut p1, mut q0, mut q1) = (I::zero(), I::one(), I::one(), I::zero());
153    loop {
154        let k = a.clone() / b.clone();
155        let p = k.clone() * p1.clone() + p0;
156        p0 = std::mem::replace(&mut p1, p);
157        let q = k * q1.clone() + q0;
158        q0 = std::mem::replace(&mut q1, q);
159        if let Some(answer) = done(&p1, &q1) { return answer; }
160        let r = a % b.clone();
161        (a, b) = (b, r);
162    }
163}
164
165/// [`Tolerant::gcd`]'s walk at tolerance `t`, in fixed width, for finite `x ≥ y > 0` whose ratio has the parts that `ratio_parts` gives. It applies where both parts fit in 64 bits. Every convergent is then below 2^64, and every product below 2^128. `None` where the parts don't fit, where a step's test is too close to decide in floats, or where the result needs a division by an integer above 2^53. The exact walk decides those, so both give the same result.
166fn fixed_gcd(x: f64, y: f64, (mx, sx): (u64, u32), (my, sy): (u64, u32), t: f64) -> Option<f64> {
167    let shifted = |m: u64, s: u32| (m.leading_zeros() >= s).then(|| m << s);
168    let (n, d) = (shifted(mx, sx)?, shifted(my, sy)?);
169    convergents(n, d, |&p, &q| {
170        // `|p/q − n/d| ≤ t·n/d`, multiplied through by `q·d`. The float estimates decide unless they lie within 1e-15 of each other.
171        let gap = (u128::from(p) * u128::from(d)).abs_diff(u128::from(q) * u128::from(n));
172        let bound = t * (u128::from(n) * u128::from(q)) as f64;
173        let within = if gap == 0 { true }
174        else if bound < 0.5 { false } else {
175            let g = gap as f64;
176            if g <= bound * (1.0 - 1e-15) { true } else if g >= bound * (1.0 + 1e-15) { false } else { return Some(None); }
177        };
178        // The result is `x / p`, which IEEE division rounds as the exact walk does while `p` is below 2^53. An exact end has `x / p = y / q`, with the smaller `q`.
179        within.then(|| {
180            if p <= 1 << 53 { Some(x / p as f64) } else if gap == 0 && q <= 1 << 53 { Some(y / q as f64) } else { None }
181        })
182    })
183}
184
185/// The whole number nearest to `y`, with a tie going down. It's built on `floor`, an instruction on every target. In wasm, `round` is a library call, and a call in a loop stops it vectorizing.
186#[inline(always)]
187fn nearest<F: Float>(y: F) -> F { Tolerance::EXACT.nearest_whole(y).0 }
188
189/// Exact comparison, as a tolerance of 0 gives, through plain IEEE comparisons and `floor`.
190#[derive(Clone, Copy, Debug)]
191pub struct Exactly;
192
193impl Tolerant for Exactly {
194    #[inline]
195    fn value(self) -> f64 { 0.0 }
196    #[inline]
197    fn close<F: Float>(self, _: F, _: F) -> bool { false }
198    #[inline]
199    fn floor_in<F: Float, O: recipe::Ops<F> + ?Sized>(self, o: &O, y: O::V) -> O::V { recipe::exact_floor(o, y) }
200}
201
202impl Tolerant for Tolerance {
203    #[inline]
204    fn value(self) -> f64 { Tolerance::value(self) }
205    #[inline]
206    fn close<F: Float>(self, x: F, y: F) -> bool { recipe::close(&Floats, F::narrow(self.0), x, y) }
207    #[inline]
208    fn floor_in<F: Float, O: recipe::Ops<F> + ?Sized>(self, o: &O, y: O::V) -> O::V { recipe::floor(o, o.tolerance(F::narrow(self.0)), y) }
209}
210
211/// The complex versions of `Tolerant`'s comparisons, for every tolerance.
212pub trait TolerantComplex: Tolerant {
213    /// Whether `x` equals `y`: the magnitude of `x-y` is at most the tolerance times the larger magnitude, as Dyalog APL defines it. A number with an infinite or NaN part equals only itself, as IEEE compares it. A tolerance of 0 compares the parts exactly.
214    #[inline]
215    fn complex_equal<R: Native>(self, x: Complex<R>, y: Complex<R>) -> bool {
216        let (equal, decided) = self.squared_equal(x, y);
217        if decided { equal } else { self.scaled_equal(x, y) }
218    }
219    /// `complex_equal` through squared magnitudes, |x-y|² ≤ t² max(|x|², |y|²), with no branch or square root, and whether that test decides it. A tolerance of 0 compares the parts exactly, which always decides. Otherwise the test decides wherever the larger squared magnitude lies between 2^-800 and 2^1000 at f64, or 2^-80 and 2^120 at f32. In that range no square overflows, and t² times one doesn't underflow. A NaN part gives `false`, as IEEE compares it.
220    #[inline(always)]
221    fn squared_equal<R: Native>(self, x: Complex<R>, y: Complex<R>) -> (bool, bool) {
222        let t = R::narrow(self.value());
223        let (low, high) = if R::WIDE { (2f64.powi(-800), 2f64.powi(1000)) } else { (2f64.powi(-80), 2f64.powi(120)) };
224        let size = x.norm_sqr().max(y.norm_sqr());
225        let near = (x - y).norm_sqr() <= t * t * size;
226        let exact = t == R::zero();
227        ((exact & (x == y)) | (!exact & near), exact | ((size >= R::narrow(low)) & (size <= R::narrow(high))))
228    }
229    /// `complex_equal` from the magnitudes themselves, which `norm` computes without overflow or underflow, for the pairs that `squared_equal` leaves undecided.
230    fn scaled_equal<R: Native>(self, x: Complex<R>, y: Complex<R>) -> bool {
231        let finite = |z: Complex<R>| z.re.is_finite() & z.im.is_finite();
232        if !finite(x) || !finite(y) { return x == y; }
233        let t = R::narrow(self.value());
234        (x - y).norm() <= (x * t).norm().max((y * t).norm())
235    }
236    /// McDonnell's complex floor: the floors of both parts, plus 1 on the part with the larger fraction when the fractions add up to 1.
237    #[inline]
238    fn complex_floor<R: Native>(self, y: Complex<R>) -> Complex<R> {
239        let (a, b) = (self.floor(y.re), self.floor(y.im));
240        let (x, z) = (y.re - a, y.im - b);
241        if x + z < R::one() - R::narrow(self.value()) { Complex::new(a, b) }
242        else if x <= z { Complex::new(a, b + R::one()) }
243        else { Complex::new(a + R::one(), b) }
244    }
245    #[inline]
246    fn complex_ceil<R: Native>(self, y: Complex<R>) -> Complex<R> { -self.complex_floor(-y) }
247    /// `y` modulo `x` for complex numbers. A quotient equal to a Gaussian integer leaves no remainder. `x` = 0 gives `y`.
248    #[inline]
249    fn complex_residue<R: Native>(self, x: Complex<R>, y: Complex<R>) -> Complex<R> {
250        if x.is_zero() { return y; }
251        let q = y / x;
252        if self.complex_equal(x * Complex::new(nearest(q.re), nearest(q.im)), y) { Complex::zero() } else { y - x * self.complex_floor(q) }
253    }
254    /// The greatest common divisor of `x` and `y` as complex numbers, by Euclid's algorithm with the quotient rounded to the nearest Gaussian integer. It stops once the remainder is within the tolerance of the larger magnitude, and turns the result into the first quadrant, real part above 0. Two real numbers give `gcd`. A non-finite argument, or a remainder that stops shrinking, gives NaN.
255    fn complex_gcd<R: Native>(self, x: Complex<R>, y: Complex<R>) -> Complex<R> {
256        if x.im == R::zero() && y.im == R::zero() { return Complex::new(self.gcd(x.re, y.re), R::zero()); }
257        let nan = Complex::new(R::nan(), R::nan());
258        let scale = x.norm().max(y.norm());
259        if !scale.is_finite() { return nan; }
260        let original = if x.norm() >= y.norm() { x } else { y };
261        let t = R::narrow(self.value()) * scale;
262        let whole = |z: Complex<R>| Complex::new(z.re.round(), z.im.round());
263        let small = |z: Complex<R>| Complex::new(if z.re.abs() <= t { R::zero() } else { z.re }, if z.im.abs() <= t { R::zero() } else { z.im });
264        let (mut x, mut y) = (x, y);
265        while y.norm() > t {
266            let r = x - y * whole(x / y);
267            if !(r.norm() < y.norm()) { return nan; }
268            (x, y) = (y, r);
269        }
270        let mut x = small(x);
271        if x.is_zero() { return x; }
272        while x.re <= R::zero() || x.im < R::zero() { x = Complex::new(-x.im, x.re); }
273        small(original / whole(original / x))
274    }
275}
276impl<T: Tolerant> TolerantComplex for T {}
277
278/// Runs `$body` with `$t` bound to `Exactly` when the `Tolerance` `$tolerance` is 0, and to the tolerance otherwise. Each binding has its own compiled copy of `$body`. The choice happens once, outside any loop in `$body`.
279#[macro_export]
280macro_rules! with_tolerance {
281    ($tolerance:expr, |$t:ident| $body:expr) => {{
282        let tolerance: $crate::tolerant::Tolerance = $tolerance;
283        if tolerance.is_exact() {
284            let $t = $crate::tolerant::Exactly;
285            $body
286        } else {
287            let $t = tolerance;
288            $body
289        }
290    }};
291}
292
293/// Writes a tolerant function object's forms from one item body over a tolerance `$t`, at `real` or at a list of concrete types, as `forms!` takes them, with its attributes, such as `#[inline]`. The object holds a `Tolerance`. Its slice form reads it once and runs the body's loop at `Exactly` where the tolerance is 0. Its scalar forms run the body at the `Tolerance` itself, which gives the exact result at 0 with no test for each item, so a caller's own loop over items has no branch to hoist.
294macro_rules! tolerant {
295    ($(#[$($m:tt)+])* $object:ident at real $(=> $o:ty)?: $($rest:tt)+) => { tolerant!(@forms [$(#[$($m)+])*], $object, (real $(=> $o)?), $($rest)+); };
296    ($(#[$($m:tt)+])* $object:ident at $($e:ty $(=> $o:ty)?),+: $($rest:tt)+) => { tolerant!(@forms [$(#[$($m)+])*], $object, ($($e $(=> $o)?),+), $($rest)+); };
297    (@forms [$(#[$($m:tt)+])*], $object:ident, ($($at:tt)+), |$t:ident, [$($x:ident),+]| $body:expr) => {
298        forms!($(#[$($m)+])* $object at $($at)+: |k, [$($x),+]| { let $t = k.0; $body },
299            many |k, [$($x),+], n, out| with_tolerance!(k.0, |$t| each!([$($x),+], n, out, |[$($x),+]| $body)));
300    };
301}
302
303/// `b` as 1 or 0 for an [`Indicator`]. Choosing between the two constants turns a comparison's lane mask into the result with one `and`. [`Element::truth`] converts through an integer instead, which suits the stored Booleans that `slice::masked` reads but would narrow and widen a lane mask here.
304#[inline(always)]
305fn indicated<E: Element>(b: bool) -> E where bool: crate::buffer::Source<E> {
306    if b { crate::buffer::Source::<E>::read(true) } else { crate::buffer::Source::<E>::read(false) }
307}
308
309/// Writes an [`Indicator`]'s forms for the tolerant comparison `$object` at each float type `$e`, through `forms!`: its item body over a tolerance `$t` as 1 or 0, and a slice form that compares many items with one number through `against`, writing each float in the same loop.
310macro_rules! indicator {
311    ($object:ident: |$t:ident, [$x:ident, $y:ident]| $body:expr, $left:ident, $right:ident, $($e:ty),+) => {$(
312        forms!([Indicator<$object>] at $e: |k, [$x, $y]| crate::slice::Total(indicated::<$e>({ let $t = k.0.0; $body })),
313            many |k, [$x, $y], n, out| match against(k.0.0, $x, $y, n, out, Band::$left, Band::$right, |xs, lo, hi, band, out| in_band(xs, lo, hi, band, out, indicated::<$e>)) {
314                Some(ok) => ok,
315                None => with_tolerance!(k.0.0, |$t| each!([$x, $y], n, out, |[$x, $y]| crate::slice::Total(indicated::<$e>($body)))),
316            });
317    )+};
318}
319
320/// Writes a tolerant comparison's forms at `real`: its item body over a tolerance `$t`, at the `Tolerance` itself, as `tolerant!` writes them, and a slice form that compares many items with one number through `against`. `$left` is the band an item must lie in when it is on the left of the one number, and `$right` the band when it is on the right. It also writes the comparison's [`Indicator`] forms at `f32` and `f64`.
321macro_rules! compared {
322    ($object:ident: |$t:ident, [$x:ident, $y:ident]| $body:expr, $left:ident, $right:ident) => {
323        forms!($object at real => bool: |k, [$x, $y]| { let $t = k.0; $body },
324            many |k, [$x, $y], n, out| match against(k.0, $x, $y, n, out, Band::$left, Band::$right, Banded::banded) {
325                Some(ok) => ok,
326                None => with_tolerance!(k.0, |$t| each!([$x, $y], n, out, |[$x, $y]| $body)),
327            });
328        indicator!($object: |$t, [$x, $y]| $body, $left, $right, f32, f64);
329        impl Banding for $object { const BANDS: (Band, Band) = (Band::$left, Band::$right); }
330    };
331}
332
333/// Where an item must lie against the range of floats equal to one number (`Tolerant::range`) for a comparison with that number to hold. `NotAbove` and `NotBelow` include the range itself.
334#[derive(Clone, Copy)]
335pub enum Band { Inside, Outside, Below, NotAbove, Above, NotBelow }
336
337impl Band {
338    /// Whether `v` lies in this band against the range `lo` to `hi`.
339    #[inline(always)]
340    fn holds<F: Float>(self, lo: F, hi: F, v: F) -> bool { recipe::in_band(&Floats, self, lo, hi, v) }
341}
342
343/// The bands that an item must lie in for a comparison with one number to hold, when the item is on the left of that number and when it's on the right (`against`).
344pub trait Banding { const BANDS: (Band, Band); }
345
346/// Comparison `k` of `v` with the one number `c`, at tolerance `t`, as steps through `o`, as `k`'s slice form compares many items with one number: the band that `v` must lie in against the range of floats equal to `c` (`Tolerant::range`). `left` says whether `v` is the left argument. `None` where `c` is NaN.
347pub fn against_steps<K: Banding, F: Float, O: recipe::Ops<F> + ?Sized>(_k: K, t: Tolerance, o: &O, c: F, v: O::V, left: bool) -> Option<O::B> {
348    let (lo, hi) = t.range(c)?;
349    let band = if left { K::BANDS.0 } else { K::BANDS.1 };
350    let (lo, hi) = (o.number(lo), o.number(hi));
351    Some(recipe::in_band(o, band, lo, hi, v))
352}
353
354/// Runs `$body` with `$b` naming the band `$band` holds, as a constant item in each arm. A closure in `$body` then compiles with the band folded in. It doesn't capture a variable, which would leave each item to test the band.
355macro_rules! with_band {
356    ($band:expr, |$b:ident| $body:expr) => {
357        match $band {
358            Band::Inside => { const $b: Band = Band::Inside; $body }
359            Band::Outside => { const $b: Band = Band::Outside; $body }
360            Band::Below => { const $b: Band = Band::Below; $body }
361            Band::NotAbove => { const $b: Band = Band::NotAbove; $body }
362            Band::Above => { const $b: Band = Band::Above; $body }
363            Band::NotBelow => { const $b: Band = Band::NotBelow; $body }
364        }
365    };
366}
367
368/// Writes into `out`, through `from`, whether each item of `xs` lies in `band` against the range `lo` to `hi`. Its loop, through `pairs_into`, vectorizes.
369#[inline(always)]
370fn in_band<F: Float, O>(xs: &[F], lo: F, hi: F, band: Band, out: &mut [std::mem::MaybeUninit<O>], from: impl Fn(bool) -> O + Copy) {
371    with_band!(band, |B| pairs_into(
372        Arg::Many(xs), Arg::One(()), xs.len(), out, #[inline(always)] |v, ()| (from(B.holds(lo, hi, v)), true)
373    ));
374}
375
376/// The float types that `against` compares with one number to give Booleans. The default writes them through `in_band`. Wasm's `f64` and `f32` override it in `narrowed`.
377pub trait Banded: Float {
378    /// Writes into `out` whether each item of `xs` lies in `band` against the range `lo` to `hi`.
379    #[inline(always)]
380    fn banded(xs: &[Self], lo: Self, hi: Self, band: Band, out: &mut [std::mem::MaybeUninit<bool>]) { in_band(xs, lo, hi, band, out, |b| b) }
381}
382#[cfg(not(all(target_arch = "wasm32", target_feature = "simd128")))]
383impl Banded for f32 {}
384#[cfg(not(all(target_arch = "wasm32", target_feature = "simd128")))]
385impl Banded for f64 {}
386
387/// Compares each item of the `Many` argument with the `One` argument `c`, through the range of floats equal to `c`, worked out once. Each item then takes one or two plain comparisons, which `banded` writes: Booleans through `Banded`, or an `Indicator`'s floats through `in_band`. `left` is the band an item on the left of `c` must lie in, and `right` the band for an item on its right. `None`, writing nothing, unless exactly one argument is `One` and `c` isn't NaN.
388#[inline(always)]
389pub fn against<F: Float, O>(
390    t: Tolerance, x: Arg<'_, F>, y: Arg<'_, F>, n: usize, out: &mut [std::mem::MaybeUninit<O>], left: Band, right: Band,
391    banded: impl FnOnce(&[F], F, F, Band, &mut [std::mem::MaybeUninit<O>])) -> Option<bool> {
392    let (items, (lo, hi), band) = match (x, y) {
393        (Arg::Many(xs), Arg::One(c)) => (xs, t.range(c)?, left), (Arg::One(c), Arg::Many(ys)) => (ys, t.range(c)?, right), _ => return None
394    };
395    banded(&items[..n], lo, hi, band, &mut out[..n]);
396    Some(true)
397}
398
399/// A comparison's results as floats, 1 where it holds and 0 where it doesn't, as a [`Binary`](crate::Binary) at `f32` and `f64` for each comparison here. A caller that computes in floats reads a comparison's results this way, rather than storing Booleans and converting them. Its slice form compares each item and writes its float in one loop.
400#[derive(Clone, Copy, Debug)]
401pub struct Indicator<K>(pub K);
402
403/// Wasm has no instruction that narrows a 64-bit lane to a byte. LLVM writes a float comparison's results two items at a time, in about ten instructions for each item. Where it can prove that lanes hold masks, it also replaces wasm's narrowing instructions with slower code. Here each block of 16 items compares with vector instructions, then shuffles the first byte of each mask into place: 7 shuffles for `f64`'s 8 vectors, and 3 for `f32`'s 4.
404#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
405mod narrowed {
406    use super::{Band, Banded};
407    use core::arch::wasm32::*;
408    use std::mem::MaybeUninit;
409
410    /// Each type's vector instructions. `$join` gathers the first byte of each lane's mask, in order.
411    macro_rules! narrowed {
412        ($($t:ty: $lanes:literal, $splat:ident, $lt:ident, $le:ident, $gt:ident, $ge:ident, $join:ident;)+) => {$(
413            impl Banded for $t {
414                fn banded(xs: &[$t], lo: $t, hi: $t, band: Band, out: &mut [MaybeUninit<bool>]) {
415                    let (l, h) = ($splat(lo), $splat(hi));
416                    with_band!(band, |B| blocks::<$t, $lanes, { 16 / $lanes }>(xs, out, $join, #[inline(always)] |v| match B {
417                        Band::Inside => v128_and($le(l, v), $le(v, h)),
418                        Band::Outside => v128_not(v128_and($le(l, v), $le(v, h))),
419                        Band::Below => $lt(v, l),
420                        Band::NotAbove => $le(v, h),
421                        Band::Above => $gt(v, h),
422                        Band::NotBelow => $ge(v, l),
423                    }, #[inline(always)] |v| B.holds(lo, hi, v)))
424                }
425            }
426        )+};
427    }
428    narrowed! {
429        f64: 2, f64x2_splat, f64x2_lt, f64x2_le, f64x2_gt, f64x2_ge, join64;
430        f32: 4, f32x4_splat, f32x4_lt, f32x4_le, f32x4_gt, f32x4_ge, join32;
431    }
432
433    /// Writes `mask` on each block of 16 items of `xs` into `out` through `join`, and `holds` on each item left over.
434    #[inline(always)]
435    fn blocks<T: Copy, const LANES: usize, const N: usize>(
436        xs: &[T], out: &mut [MaybeUninit<bool>], join: impl Fn([v128; N]) -> v128, mask: impl Fn(v128) -> v128, holds: impl Fn(T) -> bool) {
437        let (blocks, rest) = out.as_chunks_mut::<16>();
438        let (whole, tail) = xs.as_chunks::<16>();
439        for (block, x) in blocks.iter_mut().zip(whole) {
440            // SAFETY: each load reads `LANES` of the block's 16 items.
441            let masks = std::array::from_fn(|k| mask(unsafe { v128_load(x.as_ptr().add(LANES * k).cast()) }));
442            // SAFETY: the store writes the block's 16 slots, each a byte of 0 or 1.
443            unsafe { v128_store(block.as_mut_ptr().cast(), v128_and(join(masks), u8x16_splat(1))) }
444        }
445        for (slot, &x) in rest.iter_mut().zip(tail) { slot.write(holds(x)); }
446    }
447
448    #[inline(always)]
449    fn join64(m: [v128; 8]) -> v128 {
450        let four = |a, b| i8x16_shuffle::<0, 8, 16, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>(a, b);
451        let eight = |a, b| i8x16_shuffle::<0, 1, 2, 3, 16, 17, 18, 19, 0, 0, 0, 0, 0, 0, 0, 0>(a, b);
452        let (low, high) = (eight(four(m[0], m[1]), four(m[2], m[3])), eight(four(m[4], m[5]), four(m[6], m[7])));
453        i8x16_shuffle::<0, 1, 2, 3, 4, 5, 6, 7, 16, 17, 18, 19, 20, 21, 22, 23>(low, high)
454    }
455
456    #[inline(always)]
457    fn join32(m: [v128; 4]) -> v128 {
458        let eight = |a, b| i8x16_shuffle::<0, 4, 8, 12, 16, 20, 24, 28, 0, 0, 0, 0, 0, 0, 0, 0>(a, b);
459        i8x16_shuffle::<0, 1, 2, 3, 4, 5, 6, 7, 16, 17, 18, 19, 20, 21, 22, 23>(eight(m[0], m[1]), eight(m[2], m[3]))
460    }
461}
462
463/// [`Tolerant::equal`], or [`TolerantComplex::complex_equal`] for complex items, at the tolerance it holds, as a function object.
464#[derive(Clone, Copy, Debug, PartialEq)]
465pub struct Equal(pub Tolerance);
466compared!(Equal: |t, [x, y]| t.equal(x, y), Inside, Inside);
467forms!(Equal at Complex<f32> => bool, Complex<f64> => bool: |k, [x, y]| k.0.complex_equal(x, y),
468    many |k, [x, y], n, out| with_tolerance!(k.0, |t| complex_equals(t, [x, y], n, out, false)));
469
470/// The negation of [`Equal`] at the tolerance it holds, as a function object, for real and complex items.
471#[derive(Clone, Copy, Debug, PartialEq)]
472pub struct NotEqual(pub Tolerance);
473compared!(NotEqual: |t, [x, y]| !t.equal(x, y), Outside, Outside);
474forms!(NotEqual at Complex<f32> => bool, Complex<f64> => bool: |k, [x, y]| !k.0.complex_equal(x, y),
475    many |k, [x, y], n, out| with_tolerance!(k.0, |t| complex_equals(t, [x, y], n, out, true)));
476
477/// `Equal`'s slice form on complex items, or with `negate` `NotEqual`'s. Each `CHUNK` of items splits into slices of real and imaginary parts, as in `slice::split`, so `squared_equal` runs over plain slices with no branch and vectorizes. `scaled_equal` then takes the pairs it leaves undecided, one at a time. No result fails.
478#[inline(always)]
479fn complex_equals<T: TolerantComplex, R: Native>(
480    t: T, [x, y]: [Arg<'_, Complex<R>>; 2], n: usize, out: &mut [std::mem::MaybeUninit<bool>], negate: bool) -> bool {
481    if pairs_into(x, y, n, out, #[inline(always)] |a, b| {
482        let (equal, decided) = t.squared_equal(a, b);
483        (equal != negate, decided)
484    }) { return true; }
485    let out = unsafe { crate::slice::written(out) };
486    for i in 0..n { let (a, b) = (x.nth(i), y.nth(i)); if !t.squared_equal(a, b).1 { out[i] = t.scaled_equal(a, b) != negate } }
487    true
488}
489
490/// [`Tolerant::less`] at the tolerance it holds, as a function object.
491#[derive(Clone, Copy, Debug, PartialEq)]
492pub struct Less(pub Tolerance);
493compared!(Less: |t, [x, y]| t.less(x, y), Below, Above);
494
495/// [`Tolerant::less_equal`] at the tolerance it holds, as a function object.
496#[derive(Clone, Copy, Debug, PartialEq)]
497pub struct LessEqual(pub Tolerance);
498compared!(LessEqual: |t, [x, y]| t.less_equal(x, y), NotAbove, NotBelow);
499
500/// [`Tolerant::greater`] at the tolerance it holds, as a function object.
501#[derive(Clone, Copy, Debug, PartialEq)]
502pub struct Greater(pub Tolerance);
503compared!(Greater: |t, [x, y]| t.greater(x, y), Above, Below);
504
505/// [`Tolerant::greater_equal`] at the tolerance it holds, as a function object.
506#[derive(Clone, Copy, Debug, PartialEq)]
507pub struct GreaterEqual(pub Tolerance);
508compared!(GreaterEqual: |t, [x, y]| t.greater_equal(x, y), NotBelow, NotAbove);
509
510/// A tolerant comparison as steps of `recipe`'s formulas, which a compiler records through its `recipe::Ops`: the composition that `Tolerant`'s method of the same name uses, with `recipe::close` at the object's tolerance and the arguments in the same order.
511pub trait Stepped {
512    /// The comparison of `x` and `y` through `o`, at float type `F`.
513    fn steps<F: Float, O: recipe::Ops<F> + ?Sized>(self, o: &O, x: O::V, y: O::V) -> O::B;
514}
515
516/// `recipe::close` of `x` and `y` at `o`'s tolerance for `t`: `t` itself, or a recording backend's runtime setting slot.
517#[inline(always)]
518fn close_steps<F: Float, O: recipe::Ops<F> + ?Sized>(t: Tolerance, o: &O, x: O::V, y: O::V) -> O::B {
519    recipe::close(o, o.tolerance(F::narrow(t.value())), x, y)
520}
521
522/// Whether `o` holds the tolerance for `t` as zero before the loop (`Ops::constant`), where a comparison is exact and takes no tolerance steps.
523#[inline(always)]
524fn exact<F: Float, O: recipe::Ops<F> + ?Sized>(t: impl Tolerant, o: &O) -> bool {
525    o.constant(o.tolerance(F::narrow(t.value()))).is_some_and(|t| t == F::zero())
526}
527
528impl Stepped for Equal {
529    #[inline(always)]
530    fn steps<F: Float, O: recipe::Ops<F> + ?Sized>(self, o: &O, x: O::V, y: O::V) -> O::B {
531        if exact(self.0, o) { return o.equal(x, y); }
532        recipe::equal(o, x, y, |o| close_steps(self.0, o, x, y))
533    }
534}
535
536impl Stepped for NotEqual {
537    #[inline(always)]
538    fn steps<F: Float, O: recipe::Ops<F> + ?Sized>(self, o: &O, x: O::V, y: O::V) -> O::B { let equal = Equal(self.0).steps(o, x, y); o.not(equal) }
539}
540
541impl Stepped for Less {
542    #[inline(always)]
543    fn steps<F: Float, O: recipe::Ops<F> + ?Sized>(self, o: &O, x: O::V, y: O::V) -> O::B {
544        if exact(self.0, o) { return o.less(x, y); }
545        recipe::less(o, x, y, |o| close_steps(self.0, o, x, y))
546    }
547}
548
549impl Stepped for LessEqual {
550    #[inline(always)]
551    fn steps<F: Float, O: recipe::Ops<F> + ?Sized>(self, o: &O, x: O::V, y: O::V) -> O::B {
552        if exact(self.0, o) { return o.less_equal(x, y); }
553        recipe::less_equal(o, x, y, |o| close_steps(self.0, o, x, y))
554    }
555}
556
557impl Stepped for Greater {
558    #[inline(always)]
559    fn steps<F: Float, O: recipe::Ops<F> + ?Sized>(self, o: &O, x: O::V, y: O::V) -> O::B { Less(self.0).steps(o, y, x) }
560}
561
562impl Stepped for GreaterEqual {
563    #[inline(always)]
564    fn steps<F: Float, O: recipe::Ops<F> + ?Sized>(self, o: &O, x: O::V, y: O::V) -> O::B { LessEqual(self.0).steps(o, y, x) }
565}
566
567/// [`Tolerant::matches`] at the tolerance it holds, as a function object: `equal`, except that NaN matches NaN.
568#[derive(Clone, Copy, Debug, PartialEq)]
569pub struct Matches(pub Tolerance);
570compared!(Matches: |t, [x, y]| t.matches(x, y), Inside, Inside);
571
572/// [`Tolerant::compare`] at the tolerance it holds, as a function object. NaN orders after every number and equal to NaN, as [`Tolerant::matches`] has NaN match NaN.
573#[derive(Clone, Copy, Debug, PartialEq)]
574pub struct Compare(pub Tolerance);
575tolerant!(Compare at real => Ordering: |t, [x, y]| t.compare(x, y).unwrap_or(x.is_nan().cmp(&y.is_nan())));
576
577/// [`Tolerant::floor`], or [`TolerantComplex::complex_floor`] for complex items, at the tolerance it holds, as a function object.
578#[derive(Clone, Copy, Debug, PartialEq)]
579pub struct Floor(pub Tolerance);
580tolerant!(#[inline] Floor at real: |t, [y]| Total(t.floor(y)));
581tolerant!(Floor at Complex<f32>, Complex<f64>: |t, [y]| Total(t.complex_floor(y)));
582
583/// [`Tolerant::ceil`], or [`TolerantComplex::complex_ceil`] for complex items, at the tolerance it holds, as a function object.
584#[derive(Clone, Copy, Debug, PartialEq)]
585pub struct Ceil(pub Tolerance);
586tolerant!(#[inline] Ceil at real: |t, [y]| Total(t.ceil(y)));
587tolerant!(Ceil at Complex<f32>, Complex<f64>: |t, [y]| Total(t.complex_ceil(y)));
588
589/// [`Tolerant::residue`], or [`TolerantComplex::complex_residue`] for complex items, at the tolerance it holds, as a function object: `y` modulo `x`, for arguments `x` and `y` in that order. At no tolerance, where `y/x` passes the float's precision ([`far`]), the rounded quotient has lost its low digits, and [`exact_residue`] gives the residue. The slice form recomputes those items in a second pass. Its `i64` form is [`integer::residue`](crate::integer::residue), which needs no tolerance.
590#[derive(Clone, Copy, Debug, PartialEq)]
591pub struct Residue(pub Tolerance);
592forms!(#[inline] Residue at real: |k, [x, y]| Total(if k.0.is_exact() { exact_residue(x, y) } else { k.0.residue(x, y) }),
593many |k, [x, y], n, out| with_tolerance!(k.0, |t| {
594    if t.value() != 0.0 { return each!([x, y], n, out, |[x, y]| Total(t.residue(x, y))); }
595    if !each!([x, y], n, out, |[x, y]| (t.residue(x, y), near(x, y))) { replace_far(x, y, n, out); }
596    true
597}));
598tolerant!(Residue at Complex<f32>, Complex<f64>: |t, [x, y]| t.complex_residue(x, y));
599
600/// Recomputes through [`exact_residue`] the residue in `out` of each item that isn't [`near`]. Of those, only the items that [`far`] picks change. The slice form runs this only where its loop finds an item that isn't near.
601fn replace_far<F: Float>(x: Arg<'_, F>, y: Arg<'_, F>, n: usize, out: &mut [std::mem::MaybeUninit<F>]) {
602    for (i, r) in unsafe { crate::slice::written(&mut out[..n]) }.iter_mut().enumerate() {
603        let (x, y) = (x.nth(i), y.nth(i));
604        if !near(x, y) { *r = exact_residue(x, y); }
605    }
606}
607
608/// Whether `y/x` passes the float's precision, 2^p, for a nonzero `x`. Below it, a rounded quotient is at most one away from the whole number below `y/x`, and the residue at most one `x` away from its range. Past it, the quotient has lost its low digits, and so has the residue. Without fused multiply-add, rounding the product of `x` and the quotient loses the residue past the same bound.
609#[inline(always)]
610fn far<F: Float>(x: F, y: F) -> bool { (x != F::zero()) & (y.abs() >= F::narrow(2.0) / F::epsilon() * x.abs()) }
611
612/// Whether `|y|` is below 2^p times `|x|`. It is `!far(x, y)` without `far`'s test of `x`. Where `x` is zero or NaN it is false, and [`replace_far`] recomputes the item. The slice form's loop judges each item by `near`. x86-64 leaves that loop scalar when the judgment also tests `x == 0`, as the residue's own select does.
613#[inline(always)]
614fn near<F: Float>(x: F, y: F) -> bool { y.abs() < F::narrow(2.0) / F::epsilon() * x.abs() }
615
616/// `y` modulo `x` at no tolerance. It is [`Exactly`]'s residue, except where [`far`] picks `y/x`. There it comes from Rust's float remainder, which is exact and has `y`'s sign. Where `x`'s sign differs, the residue is that remainder plus `x`. A zero remainder gives +0, as [`Tolerant::residue`] does.
617pub(crate) fn exact_residue<F: Float>(x: F, y: F) -> F { if far(x, y) { far_residue(x, y) } else { Exactly.residue(x, y) } }
618
619/// [`exact_residue`] where [`far`] holds. It stays out of line: inlined, a loop over residues computes this remainder, a libm call, for every item.
620#[cold]
621#[inline(never)]
622fn far_residue<F: Float>(x: F, y: F) -> F { let r = y % x; if r == F::zero() { F::zero() } else if (r < F::zero()) != (x < F::zero()) { r + x } else { r } }
623
624/// The tolerant floor of rounded `x ÷ y`, as a formula object. A float quotient never fails. Integers divide exactly, give a zero divisor the type's limit of the dividend's sign, and fail on an overflowing quotient.
625#[derive(Clone, Copy, Debug, PartialEq)]
626pub struct FloorDivide(pub Tolerance);
627
628/// [`FloorDivide`] with zero divided by zero defined as zero.
629#[derive(Clone, Copy, Debug, PartialEq)]
630pub struct MathishFloorDivide(pub Tolerance);
631
632/// [`Tolerant::gcd`], or [`TolerantComplex::complex_gcd`] for complex items, at the tolerance it holds, as a function object. A non-finite argument gives NaN, which marks the item failed. Its `i64` form is [`integer::gcd`](crate::integer::gcd).
633#[derive(Clone, Copy, Debug, PartialEq)]
634pub struct Gcd(pub Tolerance);
635tolerant!(Gcd at real: |t, [x, y]| t.gcd(x, y));
636tolerant!(Gcd at Complex<f32>, Complex<f64>: |t, [x, y]| t.complex_gcd(x, y));
637
638/// The least common multiple x y / gcd(x, y), with the sign of x y, at the tolerance it holds, as a function object. Where the gcd is 0, so is the result. Its `i64` form is [`integer::lcm`](crate::integer::lcm).
639#[derive(Clone, Copy, Debug, PartialEq)]
640pub struct Lcm(pub Tolerance);
641tolerant!(Lcm at real: |t, [x, y]| lcm_from_gcd(x, y, t.gcd(x, y)));
642tolerant!(Lcm at Complex<f32>, Complex<f64>: |t, [x, y]| lcm_from_gcd(x, y, t.complex_gcd(x, y)));
643
644#[inline]
645pub(crate) fn lcm_from_gcd<T: Copy + num_traits::Num>(x: T, y: T, g: T) -> T { if g.is_zero() { g } else { x / g * y } }
646
647/// The units of `BETWEEN_CHECKS` needles that each thread looks up in one round of `index_of`. A round much shorter than this leaves too little for the other threads after `parallel::split` times its first unit: with 1 unit each, `v⍳w` on a million floats took as long on 4 threads as on one.
648const ROUND_UNITS: usize = 16;
649
650/// For each item of `y`, the position of the first item of `x` that it matches within tolerance `t`, or the length of `x` where none does. NaN matches NaN. Up to `search::SCANNED` items of `y` each take one pass over `x`, which compares every item with the range of floats equal to it. When every item of both equals a whole number below `whole_limit(t)`, the search indexes those whole numbers (`search::index_of`), then checks that each item of `y` matches the first item of `x` with its whole number. When one doesn't, as two floats on either side of one whole number may not, the search takes the buckets below. Otherwise, with `Exactly`, the search indexes order keys. With a `Tolerance`, it files the items of `x` in buckets of neighbouring floats (`Near`), and compares each item of `y` with the items in the two buckets nearest it. The lookups in the index or the buckets split across threads, in rounds of `ROUND_UNITS` × `BETWEEN_CHECKS` items of `y` for each of `parallel::threads`. It checks for an interrupt on this thread before each round, and returns its error at once.
651#[inline(never)]
652pub fn index_of<F: Float>(t: impl Tolerant, x: &[F], y: &[F]) -> Result<Vec<u32>, Error> {
653    if y.len() <= search::SCANNED { return Ok(y.iter().map(|&v| scan(t, x, v) as u32).collect()); }
654    if let Some((keys, exact)) = wholes(t, x, whole_limit(t)) && let Some((whole_y, _)) = wholes(t, y, whole_limit(t)) {
655        // Two floats near one whole number each equal it, but needn't equal each other. Unless every item of `x` is a whole number, each needle must be identical to its first candidate or within tolerance of it, or the buckets below search instead. Floats near other whole numbers can't match.
656        let found = search::index_of(&keys, &whole_y);
657        if exact || found.iter().zip(y).all(|(&p, &v)| p as usize == x.len() || x[p as usize] == v || t.matches(x[p as usize], v)) { return Ok(found); }
658    }
659    if t.value() == 0.0 { return Ok(search::index_of(&order_keys(x), &order_keys(y))); }
660    let near = Near::new(t, x, |j, i| x[j].order_key() == x[i].order_key());
661    let mut found = Vec::with_capacity(y.len());
662    for round in y.chunks(ROUND_UNITS * BETWEEN_CHECKS * crate::parallel::threads()) {
663        check()?;
664        search::look_up(&mut found, round, |v| {
665            let mut first = x.len();
666            for i in near.near(v) { if i < first && t.matches(x[i], v) { first = i; } }
667            first as u32
668        });
669    }
670    Ok(found)
671}
672
673/// The position of the earliest item of each item's class within tolerance `t`. When every item equals a whole number below `whole_limit(t)`, a class holds the items equal to one whole number. Otherwise, with `Exactly`, a class holds equal floats, and with a `Tolerance`, a class is a run that `runs` gives. Two items of one class therefore needn't match each other. It checks for an interrupt after about every `BETWEEN_CHECKS` items, and returns its error at once.
674#[inline(never)]
675pub fn classes<F: Float>(t: impl Tolerant, x: &[F]) -> Result<Vec<u32>, Error> {
676    if let Some((keys, _)) = wholes(t, x, whole_limit(t)) { return Ok(search::classes(&keys)); }
677    if t.value() == 0.0 { return Ok(search::first_classes(x, F::order_key)); }
678    let mut classes = vec![0; x.len()];
679    checked_runs(t, x, |run| { for &i in run { classes[i] = run[0] as u32 } })?;
680    Ok(classes)
681}
682
683/// Whether each item of `x` is the first of its class, with the classes that `classes` gives. It checks for an interrupt as `classes` does.
684#[inline(never)]
685pub fn firsts<F: Float>(t: impl Tolerant, x: &[F]) -> Result<Vec<bool>, Error> {
686    if let Some((keys, _)) = wholes(t, x, whole_limit(t)) { return Ok(search::firsts(&keys)); }
687    if t.value() == 0.0 { return Ok(search::first_mask(x, F::order_key)); }
688    let mut firsts = vec![false; x.len()];
689    checked_runs(t, x, |run| firsts[run[0]] = true)?;
690    Ok(firsts)
691}
692
693/// `runs` for `classes` and `firsts`, with an interrupt check after about every `BETWEEN_CHECKS` items.
694fn checked_runs<F: Float>(t: impl Tolerant, x: &[F], mut each: impl FnMut(&[usize])) -> Result<(), Error> {
695    let mut since = 0;
696    runs(t, x, |run| {
697        since += run.len();
698        if since >= BETWEEN_CHECKS {
699            since = 0;
700            check()?;
701        }
702        each(run);
703        Ok(())
704    })
705}
706
707/// Calls `each` with the positions of each run of `x`, in ascending order of position. A run is a longest sequence of floats, taken in ascending order, in which each matches the next within tolerance `t`. Every float that a float matches lies in one interval (`Tolerant::range`), so a run holds every match of each of its floats. An error from `each` ends the walk, and `runs` returns it. The floats sort by their order keys, which read back as floats in memory order.
708#[inline(never)]
709pub fn runs<F: Float, E>(t: impl Tolerant, x: &[F], mut each: impl FnMut(&[usize]) -> Result<(), E>) -> Result<(), E> {
710    let (keys, order) = search::sorted(x.iter().map(|&v| v.order_key()).collect());
711    let (mut run, mut start) = (Vec::new(), 0);
712    for end in 1..=keys.len() {
713        if end < keys.len() && t.matches(F::from_order_key(keys[end - 1]), F::from_order_key(keys[end])) { continue; }
714        // Most runs hold one item, which needs no list of positions to sort.
715        if end - start == 1 { each(&[order[start] as usize])?; } else {
716            run.clear();
717            run.extend(order[start..end].iter().map(|&i| i as usize));
718            run.sort_unstable();
719            each(&run)?;
720        }
721        start = end;
722    }
723    Ok(())
724}
725
726/// Floats filed in buckets of neighbouring order keys, for tolerant search. Two floats within tolerance `t` of each other lie at most `2t/((1-t)ε)` floats apart, where `ε` is the type's epsilon. A bucket holds a power of 2 of keys, at least four times that. A key in the lower half of its bucket is therefore far enough from the upper edge that every float it matches lies in its own bucket or the neighbour on the side of its half. At f64 with the default tolerance, a bucket holds 512 keys.
727pub struct Near<F> { chains: Chains, shift: u32, width: PhantomData<F> }
728
729impl<F: Float> Near<F> {
730    /// Files each item of `x` in its bucket, except an item `i` for which `copies(j, i)` holds with an item `j` already in the bucket. A copy can't be a first match, because an earlier item matches whatever it matches.
731    pub fn new(t: impl Tolerant, x: &[F], copies: impl Fn(usize, usize) -> bool) -> Self {
732        let (t, epsilon) = (t.value(), F::epsilon().to_f64().unwrap_or(1.0));
733        let apart = (4.0 * t / ((1.0 - t) * epsilon)).ceil() + 1.0;
734        let shift = (2.0 * (apart + 1.0)).log2().ceil().min(64.0) as u32;
735        let mut near = Self { chains: Chains::new(x.len()), shift, width: PhantomData };
736        for (i, &v) in x.iter().enumerate() { near.chains.push_new(near.bucket(v).0, i, |j| copies(j, i)); }
737        near
738    }
739    /// The bucket of `y`, and whether `y` lies in its upper half.
740    #[inline]
741    fn bucket(&self, y: F) -> (u64, bool) {
742        let k = y.order_key().key() as u64;
743        (k.checked_shr(self.shift).unwrap_or(0), self.shift > 0 && (k >> (self.shift - 1)) & 1 == 1)
744    }
745    /// The positions filed near `y`: those in its bucket, then those in the neighbour on the side of its half, each in order of position.
746    #[inline]
747    pub fn near(&self, y: F) -> Links<'_> { let (b, upper) = self.bucket(y); self.chains.chains(b, if upper { b.checked_add(1) } else { b.checked_sub(1) }) }
748}
749
750/// The order key of each float, in one pass that vectorises. Exact searches hash these keys, because f32 searches ran about 6% slower when each lookup computed its own key.
751pub(crate) fn order_keys<F: Float>(x: &[F]) -> Vec<F::OrderKey> { x.iter().map(|v| v.order_key()).collect() }
752
753/// The magnitude below which each whole number equals no other whole number within tolerance `t`, nor do floats equal to different whole numbers equal each other: 1/(4t), or 2^53 for a tolerance of 0. It is never above 2^53, below which an `f64` holds every whole number.
754pub fn whole_limit(t: impl Tolerant) -> f64 { (0.25 / t.value()).min(9_007_199_254_740_992.0) }
755
756/// The magnitude below which a whole number compares with integers as it does exactly, where each integer converts to a float of `F`'s width and compares within tolerance `t`: `whole_limit(t)`, and at most 2^p, below which floats of the width hold every whole number. A residue of one integer by another, where their magnitudes add up to less than it, is exact at the width, with or without a fused multiply-add.
757pub fn integer_limit<F: Float>(t: impl Tolerant) -> f64 { whole_limit(t).min(2.0 / F::epsilon().wide()) }
758
759/// `v` as an integer, where it is exactly whole and below `integer_limit::<F>(t)` in magnitude, so that integers compare with it as with that integer.
760pub fn integer_of<F: Float>(t: impl Tolerant, v: F) -> Option<i64> {
761    let v = v.wide();
762    (v.fract() == 0.0 && v.abs() < integer_limit::<F>(t)).then_some(v as i64)
763}
764
765/// Each item of `x` as the whole number it equals within tolerance `t`, when every item equals one below `limit` in magnitude, and whether every item is exactly its whole number. A search can then index these integers in place of the floats, with `whole_limit(t)` as the limit. A `limit` of at most 2^63 keeps every whole number within `i64`. Each block of `WHOLES` items is rounded to its nearest whole numbers in one loop that vectorizes. A block with an item that isn't exactly whole then tests each item against the tolerance in a second loop. The first block that fails ends the conversion.
766pub fn wholes<F: Float>(t: impl Tolerant, x: &[F], limit: f64) -> Option<(Vec<i64>, bool)> {
767    const WHOLES: usize = 256;
768    let (limit, mut keys, mut exact) = (F::narrow(limit), Vec::with_capacity(x.len()), true);
769    for block in x.chunks(WHOLES) {
770        let (fits, whole) = unsafe {
771            crate::slice::appended(&mut keys, block.len(), |slots| {
772                // For an integral `n` below 2^51 in magnitude, the bits of `n + 1.5·2^52` minus the bits of `1.5·2^52` equal `n`. On AVX2 this conversion vectorizes and `as i64` doesn't. A block with a larger item converts each item by `as i64`.
773                const MAGIC: f64 = 6_755_399_441_055_744.0;
774                let (mut fits, mut whole, mut small) = (true, true, true);
775                for (slot, &y) in slots.iter_mut().zip(block) {
776                    let n = nearest(y);
777                    fits &= n.abs() < limit;
778                    whole &= n == y;
779                    small &= n.wide().abs() < 2_251_799_813_685_248.0;
780                    slot.write(((n.wide() + MAGIC).to_bits() as i64).wrapping_sub(MAGIC.to_bits() as i64));
781                }
782                if !small { for (slot, &y) in slots.iter_mut().zip(block) { slot.write(nearest(y).wide() as i64); } }
783                (fits, whole)
784            })
785        };
786        if !fits || !whole && !block.iter().fold(true, |equal, &y| equal & t.nearest_whole(y).1) { return None; }
787        exact &= whole;
788    }
789    Some((keys, exact))
790}
791
792/// The position of the first item of `x` that matches `v` within tolerance `t`, or the length of `x`. One pass compares each item with the range of floats equal to `v`, a block at a time (`search::first`). A NaN `v` matches the first NaN.
793fn scan<F: Float>(t: impl Tolerant, x: &[F], v: F) -> usize {
794    let found = match t.range(v) { Some((lo, hi)) => search::first(x, |h| (lo <= h) & (h <= hi)), None => search::first(x, |h: F| h.is_nan()) };
795    found.unwrap_or(x.len())
796}