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 let Some(g) = fixed_gcd(x, y, nx, dy, t) { return F::narrow(g); }
121        // The same walk on big integers, where the test `|p·d − q·n| ≤ t·n·q` is exact for `t = m·2^e`.
122        let (n, d) = (BigInt::from(nx.0) << nx.1, BigInt::from(dy.0) << dy.1);
123        let (m, e, _) = num_traits::Float::integer_decode(t);
124        let p = convergents(n.clone(), d.clone(), |p, q| {
125            let (gap, bound) = ((p * &d - q * &n).abs(), BigInt::from(m) * &n * q);
126            let within = gap.is_zero() || m != 0 && if e >= 0 { gap <= bound << e as usize } else { gap << (-e) as usize <= bound };
127            within.then(|| p.clone())
128        });
129        F::narrow((BigRational::from_float(x).unwrap() / BigRational::from_integer(p)).to_f64().unwrap())
130    }
131}
132
133/// `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.
134fn ratio_parts(x: f64, y: f64) -> ((u64, u32), (u64, u32)) {
135    let odd = |v: f64| {
136        let (m, e, _) = num_traits::Float::integer_decode(v);
137        (m >> m.trailing_zeros(), i32::from(e) + m.trailing_zeros() as i32)
138    };
139    let ((mx, ex), (my, ey)) = (odd(x), odd(y));
140    if ex >= ey { ((mx, (ex - ey) as u32), (my, 0)) } else { ((mx, 0), (my, (ey - ex) as u32)) }
141}
142
143/// 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.
144fn convergents<I: num_traits::Num + Clone, R>(n: I, d: I, mut done: impl FnMut(&I, &I) -> Option<R>) -> R {
145    let (mut a, mut b) = (n, d);
146    let (mut p0, mut p1, mut q0, mut q1) = (I::zero(), I::one(), I::one(), I::zero());
147    loop {
148        let k = a.clone() / b.clone();
149        let p = k.clone() * p1.clone() + p0;
150        p0 = std::mem::replace(&mut p1, p);
151        let q = k * q1.clone() + q0;
152        q0 = std::mem::replace(&mut q1, q);
153        if let Some(answer) = done(&p1, &q1) { return answer; }
154        let r = a % b.clone();
155        (a, b) = (b, r);
156    }
157}
158
159/// [`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.
160fn fixed_gcd(x: f64, y: f64, (mx, sx): (u64, u32), (my, sy): (u64, u32), t: f64) -> Option<f64> {
161    let shifted = |m: u64, s: u32| (m.leading_zeros() >= s).then(|| m << s);
162    let (n, d) = (shifted(mx, sx)?, shifted(my, sy)?);
163    convergents(n, d, |&p, &q| {
164        // `|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.
165        let gap = (u128::from(p) * u128::from(d)).abs_diff(u128::from(q) * u128::from(n));
166        let bound = t * (u128::from(n) * u128::from(q)) as f64;
167        let within = if gap == 0 { true }
168        else if bound < 0.5 { false } else {
169            let g = gap as f64;
170            if g <= bound * (1.0 - 1e-15) { true } else if g >= bound * (1.0 + 1e-15) { false } else { return Some(None); }
171        };
172        // 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`.
173        within.then(|| {
174            if p <= 1 << 53 { Some(x / p as f64) } else if gap == 0 && q <= 1 << 53 { Some(y / q as f64) } else { None }
175        })
176    })
177}
178
179/// 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.
180#[inline(always)]
181fn nearest<F: Float>(y: F) -> F { Tolerance::EXACT.nearest_whole(y).0 }
182
183/// Exact comparison, as a tolerance of 0 gives, through plain IEEE comparisons and `floor`.
184#[derive(Clone, Copy, Debug)]
185pub struct Exactly;
186
187impl Tolerant for Exactly {
188    #[inline]
189    fn value(self) -> f64 { 0.0 }
190    #[inline]
191    fn close<F: Float>(self, _: F, _: F) -> bool { false }
192    #[inline]
193    fn floor_in<F: Float, O: recipe::Ops<F> + ?Sized>(self, o: &O, y: O::V) -> O::V { recipe::exact_floor(o, y) }
194}
195
196impl Tolerant for Tolerance {
197    #[inline]
198    fn value(self) -> f64 { Tolerance::value(self) }
199    #[inline]
200    fn close<F: Float>(self, x: F, y: F) -> bool { recipe::close(&Floats, F::narrow(self.0), x, y) }
201    #[inline]
202    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) }
203}
204
205/// The complex versions of `Tolerant`'s comparisons, for every tolerance.
206pub trait TolerantComplex: Tolerant {
207    /// 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.
208    #[inline]
209    fn complex_equal<R: Native>(self, x: Complex<R>, y: Complex<R>) -> bool {
210        let (equal, decided) = self.squared_equal(x, y);
211        if decided { equal } else { self.scaled_equal(x, y) }
212    }
213    /// `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.
214    #[inline(always)]
215    fn squared_equal<R: Native>(self, x: Complex<R>, y: Complex<R>) -> (bool, bool) {
216        let t = R::narrow(self.value());
217        let (low, high) = if R::WIDE { (2f64.powi(-800), 2f64.powi(1000)) } else { (2f64.powi(-80), 2f64.powi(120)) };
218        let size = x.norm_sqr().max(y.norm_sqr());
219        let near = (x - y).norm_sqr() <= t * t * size;
220        let exact = t == R::zero();
221        ((exact & (x == y)) | (!exact & near), exact | ((size >= R::narrow(low)) & (size <= R::narrow(high))))
222    }
223    /// `complex_equal` from the magnitudes themselves, which `norm` computes without overflow or underflow, for the pairs that `squared_equal` leaves undecided.
224    fn scaled_equal<R: Native>(self, x: Complex<R>, y: Complex<R>) -> bool {
225        let finite = |z: Complex<R>| z.re.is_finite() & z.im.is_finite();
226        if !finite(x) || !finite(y) { return x == y; }
227        let t = R::narrow(self.value());
228        (x - y).norm() <= (x * t).norm().max((y * t).norm())
229    }
230    /// 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.
231    #[inline]
232    fn complex_floor<R: Native>(self, y: Complex<R>) -> Complex<R> {
233        let (a, b) = (self.floor(y.re), self.floor(y.im));
234        let (x, z) = (y.re - a, y.im - b);
235        if x + z < R::one() - R::narrow(self.value()) { Complex::new(a, b) }
236        else if x <= z { Complex::new(a, b + R::one()) }
237        else { Complex::new(a + R::one(), b) }
238    }
239    #[inline]
240    fn complex_ceil<R: Native>(self, y: Complex<R>) -> Complex<R> { -self.complex_floor(-y) }
241    /// `y` modulo `x` for complex numbers. A quotient equal to a Gaussian integer leaves no remainder. `x` = 0 gives `y`.
242    #[inline]
243    fn complex_residue<R: Native>(self, x: Complex<R>, y: Complex<R>) -> Complex<R> {
244        if x.is_zero() { return y; }
245        let q = y / x;
246        if self.complex_equal(x * Complex::new(nearest(q.re), nearest(q.im)), y) { Complex::zero() } else { y - x * self.complex_floor(q) }
247    }
248    /// 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.
249    fn complex_gcd<R: Native>(self, x: Complex<R>, y: Complex<R>) -> Complex<R> {
250        if x.im == R::zero() && y.im == R::zero() { return Complex::new(self.gcd(x.re, y.re), R::zero()); }
251        let nan = Complex::new(R::nan(), R::nan());
252        let scale = x.norm().max(y.norm());
253        if !scale.is_finite() { return nan; }
254        let original = if x.norm() >= y.norm() { x } else { y };
255        let t = R::narrow(self.value()) * scale;
256        let whole = |z: Complex<R>| Complex::new(z.re.round(), z.im.round());
257        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 });
258        let (mut x, mut y) = (x, y);
259        while y.norm() > t {
260            let r = x - y * whole(x / y);
261            if !(r.norm() < y.norm()) { return nan; }
262            (x, y) = (y, r);
263        }
264        let mut x = small(x);
265        if x.is_zero() { return x; }
266        while x.re <= R::zero() || x.im < R::zero() { x = Complex::new(-x.im, x.re); }
267        small(original / whole(original / x))
268    }
269}
270impl<T: Tolerant> TolerantComplex for T {}
271
272/// 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`.
273#[macro_export]
274macro_rules! with_tolerance {
275    ($tolerance:expr, |$t:ident| $body:expr) => {{
276        let tolerance: $crate::tolerant::Tolerance = $tolerance;
277        if tolerance.is_exact() {
278            let $t = $crate::tolerant::Exactly;
279            $body
280        } else {
281            let $t = tolerance;
282            $body
283        }
284    }};
285}
286
287/// 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.
288macro_rules! tolerant {
289    ($(#[$($m:tt)+])* $object:ident at real $(=> $o:ty)?: $($rest:tt)+) => { tolerant!(@forms [$(#[$($m)+])*], $object, (real $(=> $o)?), $($rest)+); };
290    ($(#[$($m:tt)+])* $object:ident at $($e:ty $(=> $o:ty)?),+: $($rest:tt)+) => { tolerant!(@forms [$(#[$($m)+])*], $object, ($($e $(=> $o)?),+), $($rest)+); };
291    (@forms [$(#[$($m:tt)+])*], $object:ident, ($($at:tt)+), |$t:ident, [$($x:ident),+]| $body:expr) => {
292        forms!($(#[$($m)+])* $object at $($at)+: |k, [$($x),+]| { let $t = k.0; $body },
293            many |k, [$($x),+], n, out| with_tolerance!(k.0, |$t| each!([$($x),+], n, out, |[$($x),+]| $body)));
294    };
295}
296
297/// `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.
298#[inline(always)]
299fn indicated<E: Element>(b: bool) -> E where bool: crate::buffer::Source<E> {
300    if b { crate::buffer::Source::<E>::read(true) } else { crate::buffer::Source::<E>::read(false) }
301}
302
303/// 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.
304macro_rules! indicator {
305    ($object:ident: |$t:ident, [$x:ident, $y:ident]| $body:expr, $left:ident, $right:ident, $($e:ty),+) => {$(
306        forms!([Indicator<$object>] at $e: |k, [$x, $y]| crate::slice::Total(indicated::<$e>({ let $t = k.0.0; $body })),
307            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>)) {
308                Some(ok) => ok,
309                None => with_tolerance!(k.0.0, |$t| each!([$x, $y], n, out, |[$x, $y]| crate::slice::Total(indicated::<$e>($body)))),
310            });
311    )+};
312}
313
314/// 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`.
315macro_rules! compared {
316    ($object:ident: |$t:ident, [$x:ident, $y:ident]| $body:expr, $left:ident, $right:ident) => {
317        forms!($object at real => bool: |k, [$x, $y]| { let $t = k.0; $body },
318            many |k, [$x, $y], n, out| match against(k.0, $x, $y, n, out, Band::$left, Band::$right, Banded::banded) {
319                Some(ok) => ok,
320                None => with_tolerance!(k.0, |$t| each!([$x, $y], n, out, |[$x, $y]| $body)),
321            });
322        indicator!($object: |$t, [$x, $y]| $body, $left, $right, f32, f64);
323        impl Banding for $object { const BANDS: (Band, Band) = (Band::$left, Band::$right); }
324    };
325}
326
327/// 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.
328#[derive(Clone, Copy)]
329pub enum Band { Inside, Outside, Below, NotAbove, Above, NotBelow }
330
331impl Band {
332    /// Whether `v` lies in this band against the range `lo` to `hi`.
333    #[inline(always)]
334    fn holds<F: Float>(self, lo: F, hi: F, v: F) -> bool { recipe::in_band(&Floats, self, lo, hi, v) }
335}
336
337/// 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`).
338pub trait Banding { const BANDS: (Band, Band); }
339
340/// 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.
341pub 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> {
342    let (lo, hi) = t.range(c)?;
343    let band = if left { K::BANDS.0 } else { K::BANDS.1 };
344    let (lo, hi) = (o.number(lo), o.number(hi));
345    Some(recipe::in_band(o, band, lo, hi, v))
346}
347
348/// 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.
349macro_rules! with_band {
350    ($band:expr, |$b:ident| $body:expr) => {
351        match $band {
352            Band::Inside => { const $b: Band = Band::Inside; $body }
353            Band::Outside => { const $b: Band = Band::Outside; $body }
354            Band::Below => { const $b: Band = Band::Below; $body }
355            Band::NotAbove => { const $b: Band = Band::NotAbove; $body }
356            Band::Above => { const $b: Band = Band::Above; $body }
357            Band::NotBelow => { const $b: Band = Band::NotBelow; $body }
358        }
359    };
360}
361
362/// 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.
363#[inline(always)]
364fn 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) {
365    with_band!(band, |B| pairs_into(
366        Arg::Many(xs), Arg::One(()), xs.len(), out, #[inline(always)] |v, ()| (from(B.holds(lo, hi, v)), true)
367    ));
368}
369
370/// 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`.
371pub trait Banded: Float {
372    /// Writes into `out` whether each item of `xs` lies in `band` against the range `lo` to `hi`.
373    #[inline(always)]
374    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) }
375}
376#[cfg(not(all(target_arch = "wasm32", target_feature = "simd128")))]
377impl Banded for f32 {}
378#[cfg(not(all(target_arch = "wasm32", target_feature = "simd128")))]
379impl Banded for f64 {}
380
381/// 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.
382#[inline(always)]
383pub fn against<F: Float, O>(
384    t: Tolerance, x: Arg<'_, F>, y: Arg<'_, F>, n: usize, out: &mut [std::mem::MaybeUninit<O>], left: Band, right: Band,
385    banded: impl FnOnce(&[F], F, F, Band, &mut [std::mem::MaybeUninit<O>])) -> Option<bool> {
386    let (items, (lo, hi), band) = match (x, y) {
387        (Arg::Many(xs), Arg::One(c)) => (xs, t.range(c)?, left), (Arg::One(c), Arg::Many(ys)) => (ys, t.range(c)?, right), _ => return None
388    };
389    banded(&items[..n], lo, hi, band, &mut out[..n]);
390    Some(true)
391}
392
393/// 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.
394#[derive(Clone, Copy, Debug)]
395pub struct Indicator<K>(pub K);
396
397/// 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.
398#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
399mod narrowed {
400    use super::{Band, Banded};
401    use core::arch::wasm32::*;
402    use std::mem::MaybeUninit;
403
404    /// Each type's vector instructions. `$join` gathers the first byte of each lane's mask, in order.
405    macro_rules! narrowed {
406        ($($t:ty: $lanes:literal, $splat:ident, $lt:ident, $le:ident, $gt:ident, $ge:ident, $join:ident;)+) => {$(
407            impl Banded for $t {
408                fn banded(xs: &[$t], lo: $t, hi: $t, band: Band, out: &mut [MaybeUninit<bool>]) {
409                    let (l, h) = ($splat(lo), $splat(hi));
410                    with_band!(band, |B| blocks::<$t, $lanes, { 16 / $lanes }>(xs, out, $join, #[inline(always)] |v| match B {
411                        Band::Inside => v128_and($le(l, v), $le(v, h)),
412                        Band::Outside => v128_not(v128_and($le(l, v), $le(v, h))),
413                        Band::Below => $lt(v, l),
414                        Band::NotAbove => $le(v, h),
415                        Band::Above => $gt(v, h),
416                        Band::NotBelow => $ge(v, l),
417                    }, #[inline(always)] |v| B.holds(lo, hi, v)))
418                }
419            }
420        )+};
421    }
422    narrowed! {
423        f64: 2, f64x2_splat, f64x2_lt, f64x2_le, f64x2_gt, f64x2_ge, join64;
424        f32: 4, f32x4_splat, f32x4_lt, f32x4_le, f32x4_gt, f32x4_ge, join32;
425    }
426
427    /// Writes `mask` on each block of 16 items of `xs` into `out` through `join`, and `holds` on each item left over.
428    #[inline(always)]
429    fn blocks<T: Copy, const LANES: usize, const N: usize>(
430        xs: &[T], out: &mut [MaybeUninit<bool>], join: impl Fn([v128; N]) -> v128, mask: impl Fn(v128) -> v128, holds: impl Fn(T) -> bool) {
431        let (blocks, rest) = out.as_chunks_mut::<16>();
432        let (whole, tail) = xs.as_chunks::<16>();
433        for (block, x) in blocks.iter_mut().zip(whole) {
434            // SAFETY: each load reads `LANES` of the block's 16 items.
435            let masks = std::array::from_fn(|k| mask(unsafe { v128_load(x.as_ptr().add(LANES * k).cast()) }));
436            // SAFETY: the store writes the block's 16 slots, each a byte of 0 or 1.
437            unsafe { v128_store(block.as_mut_ptr().cast(), v128_and(join(masks), u8x16_splat(1))) }
438        }
439        for (slot, &x) in rest.iter_mut().zip(tail) { slot.write(holds(x)); }
440    }
441
442    #[inline(always)]
443    fn join64(m: [v128; 8]) -> v128 {
444        let four = |a, b| i8x16_shuffle::<0, 8, 16, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>(a, b);
445        let eight = |a, b| i8x16_shuffle::<0, 1, 2, 3, 16, 17, 18, 19, 0, 0, 0, 0, 0, 0, 0, 0>(a, b);
446        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])));
447        i8x16_shuffle::<0, 1, 2, 3, 4, 5, 6, 7, 16, 17, 18, 19, 20, 21, 22, 23>(low, high)
448    }
449
450    #[inline(always)]
451    fn join32(m: [v128; 4]) -> v128 {
452        let eight = |a, b| i8x16_shuffle::<0, 4, 8, 12, 16, 20, 24, 28, 0, 0, 0, 0, 0, 0, 0, 0>(a, b);
453        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]))
454    }
455}
456
457/// [`Tolerant::equal`], or [`TolerantComplex::complex_equal`] for complex items, at the tolerance it holds, as a function object.
458#[derive(Clone, Copy, Debug, PartialEq)]
459pub struct Equal(pub Tolerance);
460compared!(Equal: |t, [x, y]| t.equal(x, y), Inside, Inside);
461forms!(Equal at Complex<f32> => bool, Complex<f64> => bool: |k, [x, y]| k.0.complex_equal(x, y),
462    many |k, [x, y], n, out| with_tolerance!(k.0, |t| complex_equals(t, [x, y], n, out, false)));
463
464/// The negation of [`Equal`] at the tolerance it holds, as a function object, for real and complex items.
465#[derive(Clone, Copy, Debug, PartialEq)]
466pub struct NotEqual(pub Tolerance);
467compared!(NotEqual: |t, [x, y]| !t.equal(x, y), Outside, Outside);
468forms!(NotEqual at Complex<f32> => bool, Complex<f64> => bool: |k, [x, y]| !k.0.complex_equal(x, y),
469    many |k, [x, y], n, out| with_tolerance!(k.0, |t| complex_equals(t, [x, y], n, out, true)));
470
471/// `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.
472#[inline(always)]
473fn complex_equals<T: TolerantComplex, R: Native>(
474    t: T, [x, y]: [Arg<'_, Complex<R>>; 2], n: usize, out: &mut [std::mem::MaybeUninit<bool>], negate: bool) -> bool {
475    if pairs_into(x, y, n, out, #[inline(always)] |a, b| {
476        let (equal, decided) = t.squared_equal(a, b);
477        (equal != negate, decided)
478    }) { return true; }
479    let out = unsafe { crate::slice::written(out) };
480    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 } }
481    true
482}
483
484/// [`Tolerant::less`] at the tolerance it holds, as a function object.
485#[derive(Clone, Copy, Debug, PartialEq)]
486pub struct Less(pub Tolerance);
487compared!(Less: |t, [x, y]| t.less(x, y), Below, Above);
488
489/// [`Tolerant::less_equal`] at the tolerance it holds, as a function object.
490#[derive(Clone, Copy, Debug, PartialEq)]
491pub struct LessEqual(pub Tolerance);
492compared!(LessEqual: |t, [x, y]| t.less_equal(x, y), NotAbove, NotBelow);
493
494/// [`Tolerant::greater`] at the tolerance it holds, as a function object.
495#[derive(Clone, Copy, Debug, PartialEq)]
496pub struct Greater(pub Tolerance);
497compared!(Greater: |t, [x, y]| t.greater(x, y), Above, Below);
498
499/// [`Tolerant::greater_equal`] at the tolerance it holds, as a function object.
500#[derive(Clone, Copy, Debug, PartialEq)]
501pub struct GreaterEqual(pub Tolerance);
502compared!(GreaterEqual: |t, [x, y]| t.greater_equal(x, y), NotBelow, NotAbove);
503
504/// 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.
505pub trait Stepped {
506    /// The comparison of `x` and `y` through `o`, at float type `F`.
507    fn steps<F: Float, O: recipe::Ops<F> + ?Sized>(self, o: &O, x: O::V, y: O::V) -> O::B;
508}
509
510/// `recipe::close` of `x` and `y` at `o`'s tolerance for `t`: `t` itself, or a recording backend's runtime setting slot.
511#[inline(always)]
512fn close_steps<F: Float, O: recipe::Ops<F> + ?Sized>(t: Tolerance, o: &O, x: O::V, y: O::V) -> O::B {
513    recipe::close(o, o.tolerance(F::narrow(t.value())), x, y)
514}
515
516/// Whether `o` holds the tolerance for `t` as zero before the loop (`Ops::constant`), where a comparison is exact and takes no tolerance steps.
517#[inline(always)]
518fn exact<F: Float, O: recipe::Ops<F> + ?Sized>(t: impl Tolerant, o: &O) -> bool {
519    o.constant(o.tolerance(F::narrow(t.value()))).is_some_and(|t| t == F::zero())
520}
521
522impl Stepped for Equal {
523    #[inline(always)]
524    fn steps<F: Float, O: recipe::Ops<F> + ?Sized>(self, o: &O, x: O::V, y: O::V) -> O::B {
525        if exact(self.0, o) { return o.equal(x, y); }
526        recipe::equal(o, x, y, |o| close_steps(self.0, o, x, y))
527    }
528}
529
530impl Stepped for NotEqual {
531    #[inline(always)]
532    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) }
533}
534
535impl Stepped for Less {
536    #[inline(always)]
537    fn steps<F: Float, O: recipe::Ops<F> + ?Sized>(self, o: &O, x: O::V, y: O::V) -> O::B {
538        if exact(self.0, o) { return o.less(x, y); }
539        recipe::less(o, x, y, |o| close_steps(self.0, o, x, y))
540    }
541}
542
543impl Stepped for LessEqual {
544    #[inline(always)]
545    fn steps<F: Float, O: recipe::Ops<F> + ?Sized>(self, o: &O, x: O::V, y: O::V) -> O::B {
546        if exact(self.0, o) { return o.less_equal(x, y); }
547        recipe::less_equal(o, x, y, |o| close_steps(self.0, o, x, y))
548    }
549}
550
551impl Stepped for Greater {
552    #[inline(always)]
553    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) }
554}
555
556impl Stepped for GreaterEqual {
557    #[inline(always)]
558    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) }
559}
560
561/// [`Tolerant::matches`] at the tolerance it holds, as a function object: `equal`, except that NaN matches NaN.
562#[derive(Clone, Copy, Debug, PartialEq)]
563pub struct Matches(pub Tolerance);
564compared!(Matches: |t, [x, y]| t.matches(x, y), Inside, Inside);
565
566/// [`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.
567#[derive(Clone, Copy, Debug, PartialEq)]
568pub struct Compare(pub Tolerance);
569tolerant!(Compare at real => Ordering: |t, [x, y]| t.compare(x, y).unwrap_or(x.is_nan().cmp(&y.is_nan())));
570
571/// [`Tolerant::floor`], or [`TolerantComplex::complex_floor`] for complex items, at the tolerance it holds, as a function object.
572#[derive(Clone, Copy, Debug, PartialEq)]
573pub struct Floor(pub Tolerance);
574tolerant!(#[inline] Floor at real: |t, [y]| Total(t.floor(y)));
575tolerant!(Floor at Complex<f32>, Complex<f64>: |t, [y]| Total(t.complex_floor(y)));
576
577/// [`Tolerant::ceil`], or [`TolerantComplex::complex_ceil`] for complex items, at the tolerance it holds, as a function object.
578#[derive(Clone, Copy, Debug, PartialEq)]
579pub struct Ceil(pub Tolerance);
580tolerant!(#[inline] Ceil at real: |t, [y]| Total(t.ceil(y)));
581tolerant!(Ceil at Complex<f32>, Complex<f64>: |t, [y]| Total(t.complex_ceil(y)));
582
583/// [`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.
584#[derive(Clone, Copy, Debug, PartialEq)]
585pub struct Residue(pub Tolerance);
586forms!(#[inline] Residue at real: |k, [x, y]| Total(if k.0.is_exact() { exact_residue(x, y) } else { k.0.residue(x, y) }),
587many |k, [x, y], n, out| with_tolerance!(k.0, |t| {
588    if t.value() != 0.0 { return each!([x, y], n, out, |[x, y]| Total(t.residue(x, y))); }
589    if !each!([x, y], n, out, |[x, y]| (t.residue(x, y), near(x, y))) { replace_far(x, y, n, out); }
590    true
591}));
592tolerant!(Residue at Complex<f32>, Complex<f64>: |t, [x, y]| t.complex_residue(x, y));
593
594/// 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.
595fn replace_far<F: Float>(x: Arg<'_, F>, y: Arg<'_, F>, n: usize, out: &mut [std::mem::MaybeUninit<F>]) {
596    for (i, r) in unsafe { crate::slice::written(&mut out[..n]) }.iter_mut().enumerate() {
597        let (x, y) = (x.nth(i), y.nth(i));
598        if !near(x, y) { *r = exact_residue(x, y); }
599    }
600}
601
602/// 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.
603#[inline(always)]
604fn far<F: Float>(x: F, y: F) -> bool { (x != F::zero()) & (y.abs() >= F::narrow(2.0) / F::epsilon() * x.abs()) }
605
606/// 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.
607#[inline(always)]
608fn near<F: Float>(x: F, y: F) -> bool { y.abs() < F::narrow(2.0) / F::epsilon() * x.abs() }
609
610/// `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.
611pub(crate) fn exact_residue<F: Float>(x: F, y: F) -> F { if far(x, y) { far_residue(x, y) } else { Exactly.residue(x, y) } }
612
613/// [`exact_residue`] where [`far`] holds. It stays out of line: inlined, a loop over residues computes this remainder, a libm call, for every item.
614#[cold]
615#[inline(never)]
616fn 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 } }
617
618/// 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.
619#[derive(Clone, Copy, Debug, PartialEq)]
620pub struct FloorDivide(pub Tolerance);
621
622/// [`FloorDivide`] with zero divided by zero defined as zero.
623#[derive(Clone, Copy, Debug, PartialEq)]
624pub struct MathishFloorDivide(pub Tolerance);
625
626/// [`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).
627#[derive(Clone, Copy, Debug, PartialEq)]
628pub struct Gcd(pub Tolerance);
629tolerant!(Gcd at real: |t, [x, y]| t.gcd(x, y));
630tolerant!(Gcd at Complex<f32>, Complex<f64>: |t, [x, y]| t.complex_gcd(x, y));
631
632/// 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).
633#[derive(Clone, Copy, Debug, PartialEq)]
634pub struct Lcm(pub Tolerance);
635tolerant!(Lcm at real: |t, [x, y]| lcm_from_gcd(x, y, t.gcd(x, y)));
636tolerant!(Lcm at Complex<f32>, Complex<f64>: |t, [x, y]| lcm_from_gcd(x, y, t.complex_gcd(x, y)));
637
638#[inline]
639pub(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 } }
640
641/// 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.
642const ROUND_UNITS: usize = 16;
643
644/// 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.
645#[inline(never)]
646pub fn index_of<F: Float>(t: impl Tolerant, x: &[F], y: &[F]) -> Result<Vec<u32>, Error> {
647    if y.len() <= search::SCANNED { return Ok(y.iter().map(|&v| scan(t, x, v) as u32).collect()); }
648    if let Some((keys, exact)) = wholes(t, x, whole_limit(t)) && let Some((whole_y, _)) = wholes(t, y, whole_limit(t)) {
649        // 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.
650        let found = search::index_of(&keys, &whole_y);
651        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); }
652    }
653    if t.value() == 0.0 { return Ok(search::index_of(&order_keys(x), &order_keys(y))); }
654    let near = Near::new(t, x, |j, i| x[j].order_key() == x[i].order_key());
655    let mut found = Vec::with_capacity(y.len());
656    for round in y.chunks(ROUND_UNITS * BETWEEN_CHECKS * crate::parallel::threads()) {
657        check()?;
658        search::look_up(&mut found, round, |v| {
659            let mut first = x.len();
660            for i in near.near(v) { if i < first && t.matches(x[i], v) { first = i; } }
661            first as u32
662        });
663    }
664    Ok(found)
665}
666
667/// 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.
668#[inline(never)]
669pub fn classes<F: Float>(t: impl Tolerant, x: &[F]) -> Result<Vec<u32>, Error> {
670    if let Some((keys, _)) = wholes(t, x, whole_limit(t)) { return Ok(search::classes(&keys)); }
671    if t.value() == 0.0 { return Ok(search::first_classes(x, F::order_key)); }
672    let mut classes = vec![0; x.len()];
673    checked_runs(t, x, |run| { for &i in run { classes[i] = run[0] as u32 } })?;
674    Ok(classes)
675}
676
677/// 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.
678#[inline(never)]
679pub fn firsts<F: Float>(t: impl Tolerant, x: &[F]) -> Result<Vec<bool>, Error> {
680    if let Some((keys, _)) = wholes(t, x, whole_limit(t)) { return Ok(search::firsts(&keys)); }
681    if t.value() == 0.0 { return Ok(search::first_mask(&search::first_classes(x, F::order_key))); }
682    let mut firsts = vec![false; x.len()];
683    checked_runs(t, x, |run| firsts[run[0]] = true)?;
684    Ok(firsts)
685}
686
687/// `runs` for `classes` and `firsts`, with an interrupt check after about every `BETWEEN_CHECKS` items.
688fn checked_runs<F: Float>(t: impl Tolerant, x: &[F], mut each: impl FnMut(&[usize])) -> Result<(), Error> {
689    let mut since = 0;
690    runs(t, x, |run| {
691        since += run.len();
692        if since >= BETWEEN_CHECKS {
693            since = 0;
694            check()?;
695        }
696        each(run);
697        Ok(())
698    })
699}
700
701/// 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.
702#[inline(never)]
703pub fn runs<F: Float, E>(t: impl Tolerant, x: &[F], mut each: impl FnMut(&[usize]) -> Result<(), E>) -> Result<(), E> {
704    let (keys, order) = search::sorted(x.iter().map(|&v| v.order_key()).collect());
705    let (mut run, mut start) = (Vec::new(), 0);
706    for end in 1..=keys.len() {
707        if end < keys.len() && t.matches(F::from_order_key(keys[end - 1]), F::from_order_key(keys[end])) { continue; }
708        // Most runs hold one item, which needs no list of positions to sort.
709        if end - start == 1 { each(&[order[start] as usize])?; } else {
710            run.clear();
711            run.extend(order[start..end].iter().map(|&i| i as usize));
712            run.sort_unstable();
713            each(&run)?;
714        }
715        start = end;
716    }
717    Ok(())
718}
719
720/// 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.
721pub struct Near<F> { chains: Chains, shift: u32, width: PhantomData<F> }
722
723impl<F: Float> Near<F> {
724    /// 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.
725    pub fn new(t: impl Tolerant, x: &[F], copies: impl Fn(usize, usize) -> bool) -> Self {
726        let (t, epsilon) = (t.value(), F::epsilon().to_f64().unwrap_or(1.0));
727        let apart = (4.0 * t / ((1.0 - t) * epsilon)).ceil() + 1.0;
728        let shift = (2.0 * (apart + 1.0)).log2().ceil().min(64.0) as u32;
729        let mut near = Self { chains: Chains::new(x.len()), shift, width: PhantomData };
730        for (i, &v) in x.iter().enumerate() { near.chains.push_new(near.bucket(v).0, i, |j| copies(j, i)); }
731        near
732    }
733    /// The bucket of `y`, and whether `y` lies in its upper half.
734    #[inline]
735    fn bucket(&self, y: F) -> (u64, bool) {
736        let k = y.order_key().key() as u64;
737        (k.checked_shr(self.shift).unwrap_or(0), self.shift > 0 && (k >> (self.shift - 1)) & 1 == 1)
738    }
739    /// 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.
740    #[inline]
741    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) }) }
742}
743
744/// 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.
745pub(crate) fn order_keys<F: Float>(x: &[F]) -> Vec<F::OrderKey> { x.iter().map(|v| v.order_key()).collect() }
746
747/// 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.
748pub fn whole_limit(t: impl Tolerant) -> f64 { (0.25 / t.value()).min(9_007_199_254_740_992.0) }
749
750/// 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.
751pub fn integer_limit<F: Float>(t: impl Tolerant) -> f64 { whole_limit(t).min(2.0 / F::epsilon().wide()) }
752
753/// `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.
754pub fn integer_of<F: Float>(t: impl Tolerant, v: F) -> Option<i64> {
755    let v = v.wide();
756    (v.fract() == 0.0 && v.abs() < integer_limit::<F>(t)).then_some(v as i64)
757}
758
759/// 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.
760pub fn wholes<F: Float>(t: impl Tolerant, x: &[F], limit: f64) -> Option<(Vec<i64>, bool)> {
761    const WHOLES: usize = 256;
762    let (limit, mut keys, mut exact) = (F::narrow(limit), Vec::with_capacity(x.len()), true);
763    for block in x.chunks(WHOLES) {
764        let (fits, whole) = unsafe {
765            crate::slice::appended(&mut keys, block.len(), |slots| {
766                // 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`.
767                const MAGIC: f64 = 6_755_399_441_055_744.0;
768                let (mut fits, mut whole, mut small) = (true, true, true);
769                for (slot, &y) in slots.iter_mut().zip(block) {
770                    let n = nearest(y);
771                    fits &= n.abs() < limit;
772                    whole &= n == y;
773                    small &= n.wide().abs() < 2_251_799_813_685_248.0;
774                    slot.write(((n.wide() + MAGIC).to_bits() as i64).wrapping_sub(MAGIC.to_bits() as i64));
775                }
776                if !small { for (slot, &y) in slots.iter_mut().zip(block) { slot.write(nearest(y).wide() as i64); } }
777                (fits, whole)
778            })
779        };
780        if !fits || !whole && !block.iter().fold(true, |equal, &y| equal & t.nearest_whole(y).1) { return None; }
781        exact &= whole;
782    }
783    Some((keys, exact))
784}
785
786/// 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.
787fn scan<F: Float>(t: impl Tolerant, x: &[F], v: F) -> usize {
788    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()) };
789    found.unwrap_or(x.len())
790}