Skip to main content

deep_time/math/
sqrt.rs

1#![allow(clippy::indexing_slicing)]
2#![allow(clippy::excessive_precision)]
3#![allow(clippy::approx_constant)]
4#![allow(clippy::eq_op)]
5
6use crate::Real;
7
8const RSQRT_TAB: [u16; 128] = [
9    0xb451, 0xb2f0, 0xb196, 0xb044, 0xaef9, 0xadb6, 0xac79, 0xab43, 0xaa14, 0xa8eb, 0xa7c8, 0xa6aa,
10    0xa592, 0xa480, 0xa373, 0xa26b, 0xa168, 0xa06a, 0x9f70, 0x9e7b, 0x9d8a, 0x9c9d, 0x9bb5, 0x9ad1,
11    0x99f0, 0x9913, 0x983a, 0x9765, 0x9693, 0x95c4, 0x94f8, 0x9430, 0x936b, 0x92a9, 0x91ea, 0x912e,
12    0x9075, 0x8fbe, 0x8f0a, 0x8e59, 0x8daa, 0x8cfe, 0x8c54, 0x8bac, 0x8b07, 0x8a64, 0x89c4, 0x8925,
13    0x8889, 0x87ee, 0x8756, 0x86c0, 0x862b, 0x8599, 0x8508, 0x8479, 0x83ec, 0x8361, 0x82d8, 0x8250,
14    0x81c9, 0x8145, 0x80c2, 0x8040, 0xff02, 0xfd0e, 0xfb25, 0xf947, 0xf773, 0xf5aa, 0xf3ea, 0xf234,
15    0xf087, 0xeee3, 0xed47, 0xebb3, 0xea27, 0xe8a3, 0xe727, 0xe5b2, 0xe443, 0xe2dc, 0xe17a, 0xe020,
16    0xdecb, 0xdd7d, 0xdc34, 0xdaf1, 0xd9b3, 0xd87b, 0xd748, 0xd61a, 0xd4f1, 0xd3cd, 0xd2ad, 0xd192,
17    0xd07b, 0xcf69, 0xce5b, 0xcd51, 0xcc4a, 0xcb48, 0xca4a, 0xc94f, 0xc858, 0xc764, 0xc674, 0xc587,
18    0xc49d, 0xc3b7, 0xc2d4, 0xc1f4, 0xc116, 0xc03c, 0xbf65, 0xbe90, 0xbdbe, 0xbcef, 0xbc23, 0xbb59,
19    0xba91, 0xb9cc, 0xb90a, 0xb84a, 0xb78c, 0xb6d0, 0xb617, 0xb560,
20];
21
22#[inline]
23const fn mul32(a: u32, b: u32) -> u32 {
24    ((a as u64).wrapping_mul(b as u64) >> 32) as u32
25}
26
27#[inline]
28const fn mul64(a: u64, b: u64) -> u64 {
29    let ahi = a >> 32;
30    let alo = a & 0xffffffff;
31    let bhi = b >> 32;
32    let blo = b & 0xffffffff;
33    ahi.wrapping_mul(bhi)
34        .wrapping_add(ahi.wrapping_mul(blo) >> 32)
35        .wrapping_add(alo.wrapping_mul(bhi) >> 32)
36}
37
38/// Computes sqrt(x) using the table-driven Goldschmidt iteration
39/// from musl libc. Correctly rounded to nearest-even for all Real inputs.
40/// const, no std, no alloc friendly.
41pub const fn sqrt(x: Real) -> Real {
42    let mut ix = x.to_bits();
43    let mut top = ix >> 52;
44
45    // Special cases: subnormal, inf, nan, negative, zero
46    if top.wrapping_sub(0x001) >= 0x7fe {
47        if ix << 1 == 0 {
48            return x; // ±0.0
49        }
50        if ix == 0x7ff0_0000_0000_0000 {
51            return x; // +inf
52        }
53        if ix > 0x7ff0_0000_0000_0000 {
54            // negative or NaN → quiet NaN, preserve sign bit for -inf/-num
55            let nan_bits = 0x7ff8_0000_0000_0000 | (ix & 0x8000_0000_0000_0000);
56            return Real::from_bits(nan_bits);
57        }
58        // Subnormal: normalize by multiplying by 2^52
59        let scale = Real::from_bits(0x4330_0000_0000_0000); // 2^52
60        ix = (x * scale).to_bits();
61        top = (ix >> 52).wrapping_sub(52);
62    }
63
64    let even = top & 1;
65    let mut m = (ix << 11) | 0x8000_0000_0000_0000u64;
66    if even != 0 {
67        m >>= 1;
68    }
69    let top = (top.wrapping_add(0x3ff)) >> 1; // result exponent (biased)
70
71    // Table-driven initial reciprocal sqrt estimate + Goldschmidt iterations
72    // All vars u64 to match C closely; mul32/mul64 return u64 for simplicity
73    let three: u64 = 0xc000_0000;
74    let i = ((ix >> 46) % 128) as usize;
75    let mut r: u64 = (RSQRT_TAB[i] as u64) << 16;
76
77    let mut s: u64 = mul32((m >> 32) as u32, r as u32) as u64;
78    let mut d: u64 = mul32(s as u32, r as u32) as u64;
79    let mut u: u64 = three - d;
80    r = (mul32(r as u32, u as u32) << 1) as u64;
81    s = (mul32(s as u32, u as u32) << 1) as u64;
82
83    d = mul32(s as u32, r as u32) as u64;
84    u = three - d;
85    r = (mul32(r as u32, u as u32) << 1) as u64;
86
87    r <<= 32;
88    s = mul64(m, r);
89    d = mul64(s, r);
90    u = (three << 32) - d;
91    s = mul64(s, u);
92
93    // Final adjustment and rounding decision
94    s = (s - 2) >> 9;
95
96    let d0 = (m << 42).wrapping_sub(s.wrapping_mul(s));
97    let d1 = s.wrapping_sub(d0);
98    let _d2 = d1.wrapping_add(s).wrapping_add(1);
99
100    if (d1 >> 63) != 0 {
101        s = s.wrapping_add(1);
102    }
103    s &= 0x000f_ffff_ffff_ffff;
104    s |= top << 52;
105
106    Real::from_bits(s)
107}
108
109const SPLIT: Real = 134217728. + 1.; // 0x1p27 + 1 === (2 ^ 27) + 1
110
111const fn sq(x: Real) -> (Real, Real) {
112    let xc: Real = x * SPLIT;
113    let xh: Real = x - xc + xc;
114    let xl: Real = x - xh;
115    let hi = x * x;
116    let lo = xh * xh - hi + 2. * xh * xl + xl * xl;
117    (hi, lo)
118}
119
120/// Computes `sqrt(x² + y²)` without overflow or harmful underflow.
121///
122/// A `const fn`-compatible port of the musl `hypot` implementation.
123/// Returns `|x|` when `y` is zero, and follows IEEE special-case rules
124/// (e.g. `hypot(±∞, NaN)` returns `+∞`).
125pub const fn hypot(mut x: Real, mut y: Real) -> Real {
126    let x1p700 = Real::from_bits(0x6bb0000000000000); // 0x1p700 === 2 ^ 700
127    let x1p_700 = Real::from_bits(0x1430000000000000); // 0x1p-700 === 2 ^ -700
128
129    let mut uxi = x.to_bits();
130    let mut uyi = y.to_bits();
131    let uti;
132    let mut z: Real;
133
134    /* arrange |x| >= |y| */
135    uxi &= -1i64 as u64 >> 1;
136    uyi &= -1i64 as u64 >> 1;
137    if uxi < uyi {
138        uti = uxi;
139        uxi = uyi;
140        uyi = uti;
141    }
142
143    /* special cases */
144    let ex: i64 = (uxi >> 52) as i64;
145    let ey: i64 = (uyi >> 52) as i64;
146    x = Real::from_bits(uxi);
147    y = Real::from_bits(uyi);
148    /* note: hypot(inf,nan) == inf */
149    if ey == 0x7ff {
150        return y;
151    }
152    if ex == 0x7ff || uyi == 0 {
153        return x;
154    }
155    /* note: hypot(x,y) ~= x + y*y/x/2 with inexact for small y/x */
156    /* 64 difference is enough for ld80 double_t */
157    if ex - ey > 64 {
158        return x + y;
159    }
160
161    /* precise sqrt argument in nearest rounding mode without overflow */
162    /* xh*xh must not overflow and xl*xl must not underflow in sq */
163    z = 1.;
164    if ex > 0x3ff + 510 {
165        z = x1p700;
166        x *= x1p_700;
167        y *= x1p_700;
168    } else if ey < 0x3ff - 450 {
169        z = x1p_700;
170        x *= x1p700;
171        y *= x1p700;
172    }
173    let (hx, lx) = sq(x);
174    let (hy, ly) = sq(y);
175    z * sqrt(ly + lx + hy + hx)
176}
177
178#[cfg(all(test, feature = "std"))]
179mod sqrt_tests {
180    use super::sqrt;
181    use std::{f64, vec, vec::Vec};
182
183    #[test]
184    fn test_special_cases() {
185        assert_eq!(sqrt(0.0), 0.0);
186        assert_eq!(sqrt(-0.0), -0.0);
187        assert!(sqrt(f64::INFINITY).is_infinite() && sqrt(f64::INFINITY) > 0.0);
188        assert!(sqrt(f64::NEG_INFINITY).is_nan());
189        assert!(sqrt(-1.0).is_nan());
190        assert!(sqrt(f64::NAN).is_nan());
191        // signaling nan? but in practice quiet
192    }
193
194    #[test]
195    fn test_perfect_squares() {
196        for i in 0..100u32 {
197            let x = (i * i) as f64;
198            let r = sqrt(x);
199            assert!((r - i as f64).abs() < 1e-10 || r.is_nan());
200        }
201    }
202
203    #[test]
204    fn test_random_vs_std() {
205        // 5M deterministic LCG random normals in [1,2) — exercises table + Goldschmidt fully
206        let mut failures = 0u32;
207        let mut state: u64 = 0x123456789abcdef0;
208        for _ in 0..5_000_000 {
209            state = state.wrapping_mul(6364136223846793005).wrapping_add(1);
210            let bits = (state & 0x000f_ffff_ffff_ffff) | 0x3ff0_0000_0000_0000; // positive normal [1,2)
211            let val = f64::from_bits(bits);
212            let r1 = sqrt(val);
213            let r2 = val.sqrt();
214            if r1.to_bits() != r2.to_bits() {
215                failures += 1;
216                // if failures < 3 {
217                //     eprintln!(
218                //         "Mismatch at {:016x}: ours={:016x} std={:016x}",
219                //         bits,
220                //         r1.to_bits(),
221                //         r2.to_bits()
222                //     );
223                // }
224            }
225        }
226        assert_eq!(
227            failures, 0,
228            "Found {} mismatches in 5M random normals [1,2)",
229            failures
230        );
231    }
232
233    #[test]
234    fn test_subnormals_random() {
235        // 100k random subnormals (exp=0) — critical for normalize path
236        let mut failures = 0u32;
237        let mut state: u64 = 0xdeadbeefcafebabe;
238        for _ in 0..100_000 {
239            state = state.wrapping_mul(6364136223846793005).wrapping_add(1);
240            // subnormal: exp=0, random mantissa (low 52 bits)
241            let bits = state & 0x000f_ffff_ffff_ffff; // clears sign + exp
242            let val = f64::from_bits(bits);
243            if val == 0.0 {
244                continue;
245            } // skip zero
246            let r1 = sqrt(val);
247            let r2 = val.sqrt();
248            if r1.to_bits() != r2.to_bits() {
249                failures += 1;
250                // if failures < 3 {
251                //     eprintln!(
252                //         "Subnormal mismatch at {:016x}: ours={:016x} std={:016x}",
253                //         bits,
254                //         r1.to_bits(),
255                //         r2.to_bits()
256                //     );
257                // }
258            }
259        }
260        assert_eq!(
261            failures, 0,
262            "Found {} mismatches in 100k random subnormals",
263            failures
264        );
265    }
266
267    #[test]
268    fn test_boundaries() {
269        // Critical boundaries: min/max normal, subnormal boundary, overflow edge, powers of 2
270        let boundaries: [f64; 8] = [
271            f64::MIN_POSITIVE,                         // 2^-1022 (smallest normal)
272            f64::from_bits(0x0010_0000_0000_0000),     // 2^-1021
273            f64::from_bits(0x000f_ffff_ffff_ffff),     // largest subnormal
274            2.0f64.powi(-1074),                        // smallest positive subnormal (2^-1074)
275            f64::MAX,                                  // ~1.8e308
276            f64::from_bits(0x7fe0_0000_0000_0000),     // largest finite < inf
277            2.0f64.powi(1023),                         // 2^1023 (largest power of 2)
278            2.0f64.powi(-1022) * (1.0 + f64::EPSILON), // just above min normal
279        ];
280        for &x in &boundaries {
281            let r1 = sqrt(x);
282            let r2 = x.sqrt();
283            assert_eq!(r1.to_bits(), r2.to_bits(), "Boundary mismatch for {:e}", x);
284            // Also check sqrt(x*x) ~ |x| for positive x (within rounding), but skip underflow cases
285            if x > 0.0 && x.is_finite() && x > 1e-200 {
286                let xx = x * x;
287                if xx.is_finite() && xx.is_normal() {
288                    let r = sqrt(xx);
289                    let rel = ((r - x).abs() / x).max(0.0);
290                    assert!(
291                        rel < 1e-14 || r.is_nan(),
292                        "sqrt(x*x) not close to x for {}",
293                        x
294                    );
295                }
296            }
297        }
298    }
299
300    #[test]
301    fn test_known_hard_cases() {
302        // Known hard-to-round / exact / boundary cases — all must match std bit-exactly
303        let cases: &[f64] = &[
304            2.0,
305            0.5,
306            4.0,
307            9.0,
308            0.0,
309            f64::INFINITY,
310            1.0e-300,                              // very small normal
311            f64::from_bits(0x0010_0000_0000_0001), // just above min normal
312            1.0 + f64::EPSILON,                    // next after 1.0
313            f64::from_bits(0x7fefffffffffffff),    // largest finite
314        ];
315        for &x in cases {
316            let r = sqrt(x);
317            // bit-exact check vs Rust std (the gold standard for this platform)
318            assert_eq!(r.to_bits(), x.sqrt().to_bits(), "Bit mismatch for {:e}", x);
319        }
320    }
321
322    // Manual nextUp / nextDown
323    fn next_up(x: f64) -> f64 {
324        if x.is_nan() || x == f64::INFINITY {
325            return x;
326        }
327        if x == 0.0 {
328            return f64::from_bits(1);
329        }
330        let bits = x.to_bits();
331        if x > 0.0 {
332            f64::from_bits(bits + 1)
333        } else {
334            f64::from_bits(bits - 1)
335        }
336    }
337    fn next_down(x: f64) -> f64 {
338        if x.is_nan() || x == f64::NEG_INFINITY {
339            return x;
340        }
341        if x == -0.0 || x == 0.0 {
342            return f64::from_bits(0x8000_0000_0000_0001);
343        }
344        let bits = x.to_bits();
345        if x > 0.0 {
346            f64::from_bits(bits - 1)
347        } else {
348            f64::from_bits(bits + 1)
349        }
350    }
351
352    #[test]
353    fn test_powers_of_two() {
354        // All representable powers of 2 (even exponents must be exact, odd use std)
355        for exp in -1074i32..=1023 {
356            let x = if exp >= -1022 {
357                2.0f64.powi(exp)
358            } else {
359                // subnormal 2^exp = 2^(exp + 1074) * 2^-1074
360                f64::from_bits(1u64 << (exp + 1074))
361            };
362            if !x.is_finite() || x == 0.0 {
363                continue;
364            }
365            let r1 = sqrt(x);
366            let r2 = x.sqrt();
367            assert_eq!(
368                r1.to_bits(),
369                r2.to_bits(),
370                "Power-of-2 mismatch for 2^{}",
371                exp
372            );
373            // For even exponents, result should be exactly 2^(exp/2) when representable
374            if exp % 2 == 0 {
375                let expected_exp = exp / 2;
376                if expected_exp >= -1022 {
377                    let expected = 2.0f64.powi(expected_exp);
378                    assert_eq!(
379                        r1.to_bits(),
380                        expected.to_bits(),
381                        "Even power-of-2 not exact for 2^{}",
382                        exp
383                    );
384                }
385            }
386        }
387    }
388
389    #[test]
390    fn test_nextafter_edges() {
391        // nextUp / nextDown around critical points (0, 1, min_normal, max)
392        let mut edges: Vec<f64> = vec![
393            f64::from_bits(1),                     // smallest positive subnormal
394            f64::from_bits(0x0000_0000_0000_0002), // next subnormal
395            next_down(f64::MIN_POSITIVE),          // largest subnormal
396            f64::MIN_POSITIVE,                     // smallest normal
397            next_up(f64::MIN_POSITIVE),
398            next_down(1.0),
399            1.0,
400            next_up(1.0),
401            next_down(f64::MAX),
402            f64::MAX,
403        ];
404        // Also a few negative edges (should all produce NaN)
405        edges.push(next_up(-f64::MIN_POSITIVE)); // negative smallest normal-ish
406        for &x in &edges {
407            let r1 = sqrt(x);
408            let r2 = x.sqrt();
409            assert_eq!(
410                r1.to_bits(),
411                r2.to_bits(),
412                "nextafter edge mismatch for {:e} (bits {:016x})",
413                x,
414                x.to_bits()
415            );
416        }
417    }
418
419    #[test]
420    fn test_negative_subnormals() {
421        // All negative subnormals must produce NaN (sign bit set in result)
422        let mut state: u64 = 0xfeedface_deadbeef;
423        for _ in 0..10_000 {
424            state = state.wrapping_mul(6364136223846793005).wrapping_add(1);
425            let bits = (state & 0x000f_ffff_ffff_ffff) | 0x8000_0000_0000_0000; // negative subnormal
426            let val = f64::from_bits(bits);
427            if val == 0.0 {
428                continue;
429            }
430            let r = sqrt(val);
431            assert!(
432                r.is_nan(),
433                "Negative subnormal did not produce NaN: {:e}",
434                val
435            );
436            // sign bit should be set (negative NaN)
437            assert!(
438                r.to_bits() & 0x8000_0000_0000_0000 != 0,
439                "NaN sign bit not set for negative subnormal"
440            );
441        }
442    }
443}