Skip to main content
← PSoXide SDK guides and examples · SDK f2fec1c301f1 · All Cargo features enabled; check feature gates before use.

psx_math/
int32.rs

1//! Saturating 32-bit integer scalar helpers.
2//!
3//! The PS1 has no FPU and the engine keeps all gameplay math in
4//! i32/i16 fixed-point, so the same small scalar helpers (absolute
5//! value that cannot overflow at `MIN`, a clamp into the GTE's i16
6//! vertex range, an integer square root for vector lengths) get
7//! re-implemented next to every consumer. This module owns them
8//! once. Every function is branch-light, allocation-free, and safe
9//! on the full input domain:
10//!
11//! - `abs_*` saturate `MIN` to `MAX` instead of overflowing.
12//! - [`square_i32_saturating`] returns `i32::MAX` once the true
13//!   square would exceed i32.
14//! - [`isqrt_i32`] is the classic digit-by-digit (binary restoring)
15//!   integer square root: exact floor(sqrt(n)), no multiplies.
16//! - [`mul_q12_i32`] multiplies an i32 by a Q1.12 factor without a
17//!   64-bit intermediate, splitting whole and fractional parts so
18//!   the products stay inside i32.
19//! - [`mul_div_i32`], [`mul_shr_i32`] and [`mul_shr_trunc_i32`] are the
20//!   signed `a * b / c` forms games otherwise spell as `i64` expressions
21//!   (which link `__divdi3`): bit-identical results, native `mult`/`divu`.
22//! - [`isqrt_u64`] and [`div_u64_by_u32`] give full-width roots and
23//!   64-by-32 quotients in 32-bit operations.
24
25/// Absolute value of an `i32`, saturating `i32::MIN` to `i32::MAX`.
26#[inline]
27pub fn abs_i32(value: i32) -> i32 {
28    if value == i32::MIN {
29        i32::MAX
30    } else if value < 0 {
31        -value
32    } else {
33        value
34    }
35}
36
37/// Absolute value of an `i16`, saturating `i16::MIN` to `i16::MAX`.
38#[inline]
39pub fn abs_i16(value: i16) -> i16 {
40    if value == i16::MIN {
41        i16::MAX
42    } else if value < 0 {
43        -value
44    } else {
45        value
46    }
47}
48
49/// Clamp an `i32` into the `i16` range (the GTE's vertex domain).
50#[inline]
51pub fn clamp_i16(value: i32) -> i16 {
52    value.clamp(i16::MIN as i32, i16::MAX as i32) as i16
53}
54
55/// `value * value`, saturating to `i32::MAX` when the square would
56/// overflow (|value| > 46340, the largest i32 whose square fits).
57#[inline]
58pub fn square_i32_saturating(value: i32) -> i32 {
59    let abs = abs_i32(value);
60    if abs > 46_340 {
61        return i32::MAX;
62    }
63    abs * abs
64}
65
66/// Integer square root: exact `floor(sqrt(value))` for positive
67/// inputs, `0` for zero and negative inputs.
68///
69/// Digit-by-digit (binary restoring) method: shifts and adds only,
70/// no multiplies or divides, so it is cheap on the R3000.
71#[inline]
72pub fn isqrt_i32(value: i32) -> i32 {
73    if value <= 0 {
74        return 0;
75    }
76    isqrt_u32(value as u32) as i32
77}
78
79/// Exact floor square root across the complete unsigned 32-bit domain.
80#[inline]
81pub fn isqrt_u32(mut value: u32) -> u32 {
82    let mut root = 0u32;
83    let mut bit = 1u32 << 30;
84    while bit > value {
85        bit >>= 2;
86    }
87    while bit != 0 {
88        if value >= root + bit {
89            value -= root + bit;
90            root = (root >> 1) + bit;
91        } else {
92            root >>= 1;
93        }
94        bit >>= 2;
95    }
96    root
97}
98
99/// Floor square roots of `0..=255`, the seed of the nibble root.
100#[cfg(test)]
101const ROOT8: [u8; 256] = {
102    let mut out = [0u8; 256];
103    let mut n = 0;
104    while n < 256 {
105        let mut r = 0;
106        while (r + 1) * (r + 1) <= n {
107            r += 1;
108        }
109        out[n] = r as u8;
110        n += 1;
111    }
112    out
113};
114
115/// Exact floor square root of `high * 2^32 + low` for `high < 2^16`
116/// (inputs below `2^48`), in 32-bit operations only.
117///
118/// A 256-entry table seeds the root of the high byte, two-bit restoring
119/// steps finish the high word, then eight steps consume the low word four
120/// bits at a time. The invariant is `prefix = root^2 + remainder` with
121/// `0 <= remainder <= 2 * root`. A nibble selects `4 * root + [0..3]`: test
122/// the `+2` threshold (`16 * root + 4`), then `+1` against the updated
123/// root. The root stays below `2^24`, so the expanded remainder is below
124/// `2^29` and the threshold below `2^28`, and the sign bit of their wrapping
125/// difference is the exact comparison. Ported from Hollow Knight's
126/// `integer_root`. Through it, [`isqrt_u64`] below `2^48` takes 218 cycles a
127/// call on the emulator microbench against 908 for the 32-step `u64`
128/// restoring loop it replaced.
129#[cfg(test)]
130#[inline(always)]
131const fn isqrt_below_2_48(high: u32, low: u32) -> u32 {
132    debug_assert!(high >> 16 == 0);
133    let mut root = if high < 256 {
134        ROOT8[high as usize] as u32
135    } else {
136        let mut r = ROOT8[(high >> 8) as usize] as u32;
137        let mut remainder = (high >> 8) - r * r;
138        let mut shift = 8;
139        while shift != 0 {
140            shift -= 2;
141            remainder = (remainder << 2) | ((high >> shift) & 3);
142            let trial = (r << 2) | 1;
143            let take = (remainder >= trial) as u32;
144            remainder -= trial & take.wrapping_neg();
145            r = (r << 1) | take;
146        }
147        r
148    };
149    let mut remainder = high - root * root;
150    let mut low = low;
151    let mut step = 0;
152    while step < 8 {
153        step += 1;
154        remainder = (remainder << 4) | (low >> 28);
155        low <<= 4;
156        let mid = (root << 4) | 4;
157        let upper = (remainder.wrapping_sub(mid) >> 31) ^ 1;
158        remainder -= mid & upper.wrapping_neg();
159        root = (root << 2) | (upper << 1);
160        let trial = (root << 1) | 1;
161        let lower = (remainder >= trial) as u32;
162        remainder -= trial & lower.wrapping_neg();
163        root |= lower;
164    }
165    root
166}
167
168/// `1 / sqrt(t)` for `t = k / 256`, `k` in `64..=256`, in Q2.30: the knots
169/// [`isqrt_u64`] interpolates its seed between.
170const RSQRT_KNOTS: [u32; 193] = {
171    let mut out = [0u32; 193];
172    let mut i = 0;
173    while i < 193 {
174        // 2^30 * 16 / sqrt(k) = sqrt(2^68 / k), floor root by bisection.
175        // psx-numeric-allow-next-line: const-eval knot table, never runs on the guest
176        let target = (1u128 << 68) / (i as u128 + 64);
177        // psx-numeric-allow-next-line: const-eval knot table, never runs on the guest
178        let (mut lo, mut hi) = (0u128, 1u128 << 35);
179        while lo < hi {
180            let mid = (lo + hi).div_ceil(2);
181            if mid * mid <= target {
182                lo = mid;
183            } else {
184                hi = mid - 1;
185            }
186        }
187        // 1 / sqrt(0.25) is exactly 2: one unit less keeps y^2 below 2^32.
188        out[i] = if lo >= 1 << 31 {
189            (1 << 31) - 1
190        } else {
191            lo as u32
192        };
193        i += 1;
194    }
195    out
196};
197
198/// One `multu`: the full 64-bit product of two words.
199#[inline(always)]
200// psx-numeric-allow-next-line: MULTU returns the full HI:LO product
201const fn mul32(a: u32, b: u32) -> u64 {
202    // psx-numeric-allow-next-line: MULTU high/low product, no helper
203    (a as u64) * (b as u64)
204}
205
206/// Exact `floor(sqrt(value))` across the complete unsigned 64-bit domain,
207/// with 32-bit operations and native `multu` only (no divide).
208///
209/// The value is shifted left by an even amount until its top word `t` lies
210/// in `[2^30, 2^32)`. A chord between two of 193 knots seeds
211/// `y = 1 / sqrt(t / 2^32)` (Q2.30) and one Newton step `y = y (3 - t y^2) / 2`
212/// brings it to about 29 bits.
213/// `t * y` is then the root of the shifted value, so shifting it back gives
214/// the root within a couple of units, and `multu` checks of `r^2 <= value <
215/// (r + 1)^2` settle it exactly. Six products in all. In a boot-time bench in
216/// Hollow Knight (512 squared lengths of random 3D vectors, Timer2) a call
217/// took 226 cycles against 300 for the table-seeded nibble root it replaced,
218/// loop and call overhead included, with identical results.
219#[inline]
220// psx-numeric-allow-next-line: HL/CS/HK squared-distance roots require full u64 input; the body splits it into two words.
221pub const fn isqrt_u64(value: u64) -> u32 {
222    if value == 0 {
223        return 0;
224    }
225    // Normalize by halving steps (the R3000 has no count-leading-zeros):
226    // shift left by an even amount until the top word reaches 2^30.
227    let high = (value >> 32) as u32;
228    let (mut top, mut low, mut shift) = if high != 0 {
229        (high, value as u32, 0)
230    } else {
231        (value as u32, 0, 32)
232    };
233    let mut step = 16;
234    while step >= 2 {
235        if top >> (32 - step) == 0 {
236            top = (top << step) | (low >> (32 - step));
237            low <<= step;
238            shift += step;
239        }
240        step /= 2;
241    }
242    // The seed: the chord between the two knots around t's top byte, good
243    // to about 15 bits, so one Newton step reaches about 29.
244    let knot = (top >> 24) as usize - 64;
245    let (left, right) = (RSQRT_KNOTS[knot], RSQRT_KNOTS[knot + 1]);
246    let mut y = left - (((left - right) >> 8) * ((top >> 16) & 0xff));
247    // y^2 in Q2.30 (y < 2, so below 4); t y^2 in Q2.30; y (3 - t y^2) / 2.
248    let square = (mul32(y, y) >> 30) as u32;
249    let scaled = (mul32(top, square) >> 32) as u32;
250    y = (mul32(y, (3u32 << 30).wrapping_sub(scaled)) >> 31) as u32;
251    let mut root = (mul32(top, y) >> (30 + shift / 2)) as u32;
252    // Within a few units (a few more near the top of the domain): one
253    // product, then neighbouring squares by adds, (r +- 1)^2 = r^2 +- 2r + 1.
254    let mut square = mul32(root, root);
255    while square > value {
256        root -= 1;
257        // psx-numeric-allow-next-line: correction steps on the u64 square, add/sub only
258        square -= 2 * root as u64 + 1;
259    }
260    // psx-numeric-allow-next-line: correction steps on the u64 square, add/sub only
261    while root != u32::MAX && square + 2 * (root as u64) < value {
262        // psx-numeric-allow-next-line: correction steps on the u64 square, add/sub only
263        square += 2 * root as u64 + 1;
264        root += 1;
265    }
266    root
267}
268
269/// Multiply an `i32` by a Q1.12 factor (`4096` = 1.0) without a
270/// 64-bit intermediate.
271///
272/// The value splits into whole sectors (`value >> 12`) and the
273/// Q0.12 fraction (`value & 0xFFF`); each part multiplies the
274/// factor separately and the partial products saturate, so the
275/// result stays exact wherever the true product fits in i32 and
276/// saturates instead of wrapping where it does not.
277#[inline]
278pub fn mul_q12_i32(value: i32, q12: i32) -> i32 {
279    const Q12_SHIFT: i32 = 12;
280    const Q12_ONE: i32 = 1 << Q12_SHIFT;
281    let whole = (value >> Q12_SHIFT).saturating_mul(q12);
282    let frac = ((value & (Q12_ONE - 1)).saturating_mul(q12)) >> Q12_SHIFT;
283    whole.saturating_add(frac)
284}
285
286/// `floor(value * q12 / 4096)` through the full 64-bit product.
287///
288/// Identical to [`mul_q12_i32`] wherever that function does not saturate,
289/// which is every product that fits in `i32` (both are exactly the floored
290/// true product there; `mul_q12_i32_matches_wide_where_exact` pins it). It
291/// wraps instead of saturating beyond that range, so it is for hot paths whose
292/// inputs are bounded by construction: BSP plane distances (Q20.12 world
293/// points against Q3.12 unit normals) and segment interpolation. On MIPS this
294/// is one `mult` and a two-word shift instead of two saturating multiplies.
295#[inline(always)]
296pub fn mul_q12_i32_wide(value: i32, q12: i32) -> i32 {
297    // psx-numeric-allow-next-line: R3000 MULT natively produces this 64-bit product; no wide division or helper call
298    ((i64::from(value) * i64::from(q12)) >> 12) as i32
299}
300
301/// One base-65536 digit of a normalized long division: the quotient and
302/// remainder of `(remainder * 2^16 + next) / divisor`.
303///
304/// `divisor` has its top bit set and `remainder < divisor`, so the estimate
305/// from the divisor's upper half is at most two too large (Knuth, TAOCP vol.
306/// 2, 4.3.1, algorithm D) and two explicit corrections settle it. They are
307/// spelled out rather than looped so LLVM cannot turn them back into a wide
308/// division. Every product and sum fits 32 bits: the estimate is at most
309/// `2^16 + 1` and the divisor's lower half at most `2^16 - 1`.
310#[inline(always)]
311const fn divide_digit(remainder: u32, next: u32, divisor: u32) -> (u32, u32) {
312    const RADIX: u32 = 1 << 16;
313    let upper = divisor >> 16;
314    let lower = divisor & 0xFFFF;
315    let mut q = remainder / upper;
316    let mut r = remainder % upper;
317    if q >= RADIX || q * lower > (r << 16) + next {
318        q -= 1;
319        r += upper;
320        if r < RADIX && (q >= RADIX || q * lower > (r << 16) + next) {
321            q -= 1;
322        }
323    }
324    // The true remainder is below the divisor, so the low word alone is exact.
325    let rem = (remainder << 16)
326        .wrapping_add(next)
327        .wrapping_sub(q.wrapping_mul(divisor));
328    (q, rem)
329}
330
331/// `floor((hi * 2^32 + lo) / divisor)` for a quotient that fits `u32`, in
332/// 32-bit operations only.
333///
334/// Requires `divisor != 0` and `hi < divisor`, which is exactly the shape of
335/// a Q12 fraction (`numerator <= denominator`) or a segment interpolation
336/// (`|delta| * fraction_numerator / denominator` with the numerator at most
337/// the denominator). The R3000A divides 32 by 32 in hardware but has no
338/// 64-by-32 form. This normalizes the divisor and produces the quotient as
339/// two base-65536 digits, each from one native `divu` plus at most two
340/// corrections (Hollow Knight's `quotient32`). On the emulator microbench it
341/// costs 178 cycles a call against 510 for the 32-step restoring loop it
342/// replaced (random divisors of every magnitude). The generic
343/// compiler-builtins `u64_div_rem` before that was 1,412 bytes of branchy
344/// code and the only 64-bit divide reachable from runtime code.
345#[inline]
346pub const fn div_u64_by_u32(hi: u32, lo: u32, divisor: u32) -> u32 {
347    debug_assert!(divisor != 0 && hi < divisor);
348    let shift = divisor.leading_zeros();
349    let normalized = divisor << shift;
350    // hi < divisor keeps the shifted top word below the normalized divisor.
351    let top = if shift == 0 {
352        hi
353    } else {
354        (hi << shift) | (lo >> (32 - shift))
355    };
356    let tail = lo << shift;
357    let (upper, remainder) = divide_digit(top, tail >> 16, normalized);
358    let (lower, _) = divide_digit(remainder, tail & 0xFFFF, normalized);
359    (upper << 16) | lower
360}
361
362/// Exact `floor(a * b / divisor)` when the quotient fits `u32`.
363///
364/// Requires a nonzero divisor. R3000 MULTU supplies both product words;
365/// ordinary products use native 32-bit DIVU, and only overflowing products
366/// need the two-word restoring division. No compiler wide-division helper.
367#[inline]
368pub fn mul_div_u32(a: u32, b: u32, divisor: u32) -> u32 {
369    // psx-numeric-allow-next-line: native R3000 MULTU high/low product, no wide division
370    let product = u64::from(a) * u64::from(b);
371    let hi = (product >> 32) as u32;
372    let lo = product as u32;
373    debug_assert!(divisor != 0 && hi < divisor);
374    if hi == 0 {
375        lo / divisor
376    } else {
377        div_u64_by_u32(hi, lo, divisor)
378    }
379}
380
381/// `a * b / c` through the full 64-bit product, truncated toward zero:
382/// identical to `(i64::from(a) * i64::from(b) / i64::from(c)) as i32` for
383/// every input, with no 64-bit helper call.
384///
385/// This is the signed fixed-point workhorse: a Q-format multiply is
386/// `mul_div_i32(a, b, ONE)`, a Q-format divide `mul_div_i32(a, ONE, b)`, and
387/// a proportional interpolation `mul_div_i32(delta, elapsed, duration)`.
388///
389/// - **Rounding:** truncation toward zero, like Rust's `/` (so `-7 * 1 / 2`
390///   is `-3`, not `-4`). For floor semantics with a power-of-two divisor use
391///   [`mul_shr_i32`].
392/// - **Overflow:** where the true quotient does not fit `i32` the result
393///   wraps to its low 32 bits, exactly as the `as i32` cast above does
394///   (`mul_div_i32(i32::MIN, -1, 1) == i32::MIN`). Only that case pays a
395///   third native divide.
396/// - **`c == 0`:** panics, like the 64-bit expression.
397///
398/// On the R3000A: one `multu` of the magnitudes, then a native `divu` when
399/// the product fits 32 bits, else [`div_u64_by_u32`] (two `divu`).
400#[inline]
401pub fn mul_div_i32(a: i32, b: i32, c: i32) -> i32 {
402    let negative = (a < 0) ^ (b < 0) ^ (c < 0);
403    let divisor = c.unsigned_abs();
404    // psx-numeric-allow-next-line: native R3000 MULTU high/low product, no wide division
405    let product = u64::from(a.unsigned_abs()) * u64::from(b.unsigned_abs());
406    let hi = (product >> 32) as u32;
407    let lo = product as u32;
408    let magnitude = if hi == 0 {
409        lo / divisor
410    } else if hi < divisor {
411        div_u64_by_u32(hi, lo, divisor)
412    } else {
413        // The quotient needs more than 32 bits. Its low word is the quotient
414        // of the remainder of the high word (hi = qh * c + rh, and the qh
415        // part only adds multiples of 2^32).
416        div_u64_by_u32(hi % divisor, lo, divisor)
417    };
418    if negative {
419        magnitude.wrapping_neg() as i32
420    } else {
421        magnitude as i32
422    }
423}
424
425/// `(a * b) >> shift` through the full 64-bit product: rounds toward negative
426/// infinity like an arithmetic shift, and wraps to the low 32 bits where the
427/// shifted value does not fit `i32`.
428///
429/// Identical to `((i64::from(a) * i64::from(b)) >> shift) as i32`, which is
430/// one `mult` and a two-word shift on MIPS; this names it so games stop
431/// spelling out the `i64` cast. The Q12 case is [`mul_q12_i32_wide`].
432/// Requires `shift < 32`.
433#[inline(always)]
434pub fn mul_shr_i32(a: i32, b: i32, shift: u32) -> i32 {
435    debug_assert!(shift < 32);
436    // psx-numeric-allow-next-line: R3000 MULT natively produces this 64-bit product; no wide division or helper call
437    ((i64::from(a) * i64::from(b)) >> shift) as i32
438}
439
440/// `a * b / 2^shift`, truncated toward zero, through the full 64-bit product.
441///
442/// Identical to `(i64::from(a) * i64::from(b) / (1i64 << shift)) as i32`
443/// (and, for `shift < 31`, to `mul_div_i32(a, b, 1 << shift)`) for every input
444/// (wrapping the same way where the quotient does not fit `i32`), but costs
445/// one `mult`, a bias add and a two-word shift instead of a divide: a
446/// negative product is biased by `2^shift - 1` before the arithmetic shift.
447/// This is the Q-format multiply of code that truncates (Hollow Knight's
448/// Q16 `mulq`). Requires `shift < 32`.
449#[inline(always)]
450pub fn mul_shr_trunc_i32(a: i32, b: i32, shift: u32) -> i32 {
451    debug_assert!(shift < 32);
452    // psx-numeric-allow-next-line: R3000 MULT natively produces this 64-bit product; no wide division or helper call
453    let product = i64::from(a) * i64::from(b);
454    let bias = if product < 0 { (1i64 << shift) - 1 } else { 0 };
455    ((product + bias) >> shift) as i32
456}
457
458/// Signed Q8 texels/second, truncated toward zero and wrapped to a texture.
459/// `period` must be in `1..=256`; a zero tick rate leaves only the phase.
460#[inline]
461pub fn scroll_q8_wrapped(speed: i16, phase: u8, period: u16, tick: u32, hz: u16) -> u8 {
462    debug_assert!(period > 0 && period <= 256);
463    let period = u32::from(period);
464    if speed == 0 || hz == 0 {
465        return (u32::from(phase) % period) as u8;
466    }
467    // Truncating by hz then 256 equals truncating by their product. Dropping
468    // an entire (hz * 256 * period)-tick cycle changes the result by exactly
469    // speed * period texels, so wrapping is unchanged even at u32::MAX ticks.
470    // The tick modulus fits u32 at the maximum hz=65535 and period=256.
471    let divisor = u32::from(hz) << 8;
472    let wrap_ticks = divisor * period;
473    let tick = if tick >= wrap_ticks {
474        tick % wrap_ticks
475    } else {
476        tick
477    };
478    // The reduced quotient is < abs(speed) * period <= 2^23, hence fits i32.
479    let distance = mul_div_u32(u32::from(speed.unsigned_abs()), tick, divisor) as i32;
480    let distance = if speed < 0 { -distance } else { distance };
481    (distance + i32::from(phase)).rem_euclid(period as i32) as u8
482}
483
484/// Exact `n / d` for `0 <= n < 2^31` by one `multu` and a shift, for a
485/// divisor that is fixed across many dividends.
486///
487/// Granlund and Montgomery, "Division by invariant integers using
488/// multiplication" (1994), theorem 4.2 with `N = 31`: for `l = ceil(log2 d)`
489/// and `m = floor(2^(31+l) / d) + 1`, `floor(m * n / 2^(31+l))` equals
490/// `floor(n / d)` for every `n < 2^31`, and `m` fits 32 bits. The R3000A's
491/// `div` interlocks the pipeline for about 35 cycles and `multu` for about
492/// 12 (fewer for small operands), so a per-model depth-slot divide that ran
493/// once per drawn face pays for its `new` after a handful of faces.
494#[derive(Clone, Copy, Debug, PartialEq, Eq)]
495pub struct InvariantDivisor31 {
496    magic: u32,
497    /// `l - 1`; `u32::MAX` marks a divisor of one (the product path would
498    /// need a 33-bit magic there).
499    shift: u32,
500}
501
502impl InvariantDivisor31 {
503    /// Prepare `divisor`, which must be at least one. This is the 32-step
504    /// software divide, so prepare once where the divisor is decided (a
505    /// depth range, a table size), not per use.
506    #[inline]
507    pub const fn new(divisor: u32) -> Self {
508        debug_assert!(divisor != 0);
509        if divisor <= 1 {
510            return Self {
511                magic: 0,
512                shift: u32::MAX,
513            };
514        }
515        // ceil(log2 d) for d >= 2, so 2^(l-1) < d <= 2^l and the 64-by-32
516        // divide below has hi < divisor as it requires.
517        let l = 32 - (divisor - 1).leading_zeros();
518        let magic = div_u64_by_u32(1 << (l - 1), 0, divisor).wrapping_add(1);
519        Self {
520            magic,
521            shift: l - 1,
522        }
523    }
524
525    /// `n / divisor` for `n < 2^31`.
526    #[inline(always)]
527    pub fn divide(self, n: u32) -> u32 {
528        debug_assert!(n < 1 << 31);
529        if self.shift == u32::MAX {
530            return n;
531        }
532        // psx-numeric-allow-next-line: the high word of R3000 MULTU, no wide divide
533        let high = ((u64::from(self.magic) * u64::from(n)) >> 32) as u32;
534        high >> self.shift
535    }
536}
537
538#[cfg(test)]
539mod tests {
540    use super::*;
541
542    #[test]
543    #[cfg_attr(
544        miri,
545        ignore = "exhaustive sweep, minutes under Miri; make test runs it natively"
546    )]
547    fn unsigned_square_root_full_domain_boundaries() {
548        let check = |n: u64| {
549            let r = u64::from(isqrt_u64(n));
550            assert!(r * r <= n);
551            assert!(r == u64::from(u32::MAX) || (r + 1) * (r + 1) > n);
552        };
553        for root in [0u64, 1, 2, 255, 65535, 65536, 1 << 31, u64::from(u32::MAX)] {
554            let square = root * root;
555            for n in [
556                square.saturating_sub(1),
557                square,
558                square.saturating_add(1),
559                u64::MAX,
560            ] {
561                check(n);
562            }
563        }
564        for n in 0..100_000 {
565            check(n);
566        }
567    }
568
569    #[test]
570    #[cfg_attr(
571        miri,
572        ignore = "exhaustive sweep, minutes under Miri; make test runs it natively"
573    )]
574    fn mul_div_u32_preserves_full_width_animation_phases() {
575        let check = |a: u32, b: u32, d: u32| {
576            let expected = u64::from(a) * u64::from(b) / u64::from(d);
577            assert!(expected <= u64::from(u32::MAX));
578            assert_eq!(u64::from(mul_div_u32(a, b, d)), expected, "{a} * {b} / {d}");
579        };
580        let edges = [1, 2, 59, 60, 65535, 65536, 65537, 1 << 31, u32::MAX];
581        for d in edges {
582            for b in edges {
583                for a in [0, 1, d / 2, d - 1, d] {
584                    check(a, b, d);
585                }
586            }
587        }
588        check(u32::MAX, 1, 1);
589        check(65536, 65536, 2);
590        let mut seed = 0x5817_9513u32;
591        let mut next = || {
592            seed ^= seed << 13;
593            seed ^= seed >> 17;
594            seed ^= seed << 5;
595            seed
596        };
597        for _ in 0..100_000 {
598            let d = next().max(1);
599            check(next() % d, next(), d);
600        }
601    }
602
603    #[test]
604    #[cfg_attr(
605        miri,
606        ignore = "exhaustive sweep, minutes under Miri; make test runs it natively"
607    )]
608    fn wrapped_scroll_matches_wide_oracle_at_wrap_and_tick_boundaries() {
609        let check = |speed: i16, phase: u8, period: u16, tick: u32, hz: u16| {
610            let distance = if hz == 0 {
611                0
612            } else {
613                i64::from(speed) * i64::from(tick) / i64::from(hz) / 256
614            };
615            let expected = (distance + i64::from(phase)).rem_euclid(i64::from(period)) as u8;
616            assert_eq!(
617                scroll_q8_wrapped(speed, phase, period, tick, hz),
618                expected,
619                "speed={speed} phase={phase} period={period} tick={tick} hz={hz}"
620            );
621        };
622        // Every signed speed, including MIN (whose absolute value is 32768).
623        for speed in i16::MIN..=i16::MAX {
624            for tick in [0, 1, 59, 60, 15359, 15360, u32::MAX] {
625                check(speed, 255, 63, tick, 60);
626            }
627        }
628        // Arbitrary periods, byte wrapping, PAL/NTSC and the full rate domain.
629        for period in 1..=256 {
630            for hz in [1u16, 50, 60, 256, 512, 65535] {
631                let cycle = (u32::from(hz) << 8) * u32::from(period);
632                for tick in [0, 1, cycle - 1, cycle, cycle + 1, u32::MAX - 1, u32::MAX] {
633                    for speed in [i16::MIN, -32767, -257, -1, 0, 1, 257, i16::MAX] {
634                        for phase in [0, 127, 255] {
635                            check(speed, phase, period, tick, hz);
636                        }
637                    }
638                }
639            }
640        }
641        for phase in [0, 127, 255] {
642            check(i16::MIN, phase, 63, u32::MAX, 0);
643        }
644        let mut seed = 0x3211_8a53u32;
645        let mut next = || {
646            seed ^= seed << 13;
647            seed ^= seed >> 17;
648            seed ^= seed << 5;
649            seed
650        };
651        for _ in 0..100_000 {
652            check(
653                next() as i16,
654                next() as u8,
655                (next() % 256 + 1) as u16,
656                next(),
657                next() as u16,
658            );
659        }
660    }
661
662    #[test]
663    #[cfg_attr(
664        miri,
665        ignore = "exhaustive sweep, minutes under Miri; make test runs it natively"
666    )]
667    fn invariant_divisor_31_is_exact() {
668        let mut state = 0x9e37_79b9_7f4a_7c15u64;
669        let mut next = || {
670            state ^= state << 13;
671            state ^= state >> 7;
672            state ^= state << 17;
673            state
674        };
675        let check = |n: u32, d: u32| {
676            assert_eq!(InvariantDivisor31::new(d).divide(n), n / d, "n={n} d={d}");
677        };
678        for d in 1..=70u32 {
679            for n in 0..2048u32 {
680                check(n, d);
681            }
682            let top = (1u32 << 31) - 1;
683            for n in [top, top - 1, top - d, d, d - 1, d + 1, d * 3 - 1, d * 3] {
684                check(n, d);
685            }
686        }
687        for shift in 1..31u32 {
688            let d = 1u32 << shift;
689            for n in [0, 1, d - 1, d, d + 1, (1u32 << 31) - 1] {
690                check(n, d);
691                check(n, d - 1);
692                check(n, d + 1);
693            }
694        }
695        for _ in 0..200_000 {
696            let d = (next() as u32 >> (next() % 32) as u32).max(1);
697            let n = next() as u32 >> 1;
698            check(n, d);
699            check(n % d, d);
700            check(n - n % d, d);
701        }
702    }
703
704    #[test]
705    fn div_u64_by_u32_matches_the_wide_reference() {
706        let mut state = 0x2545_f491_4f6c_dd1du64;
707        let mut next = || {
708            state ^= state << 13;
709            state ^= state >> 7;
710            state ^= state << 17;
711            state
712        };
713        for _ in 0..20_000 {
714            let divisor = ((next() as u32) | 1).max(2);
715            let hi = (next() as u32) % divisor;
716            let lo = next() as u32;
717            let wide = ((u64::from(hi) << 32) | u64::from(lo)) / u64::from(divisor);
718            assert_eq!(u64::from(div_u64_by_u32(hi, lo, divisor)), wide);
719        }
720        assert_eq!(div_u64_by_u32(0, 0, 1), 0);
721        assert_eq!(div_u64_by_u32(u32::MAX - 1, u32::MAX, u32::MAX), u32::MAX);
722        assert_eq!(div_u64_by_u32(0, 4096 << 12, 4096), 4096);
723    }
724
725    #[test]
726    #[cfg_attr(
727        miri,
728        ignore = "exhaustive sweep, minutes under Miri; make test runs it natively"
729    )]
730    fn mul_q12_i32_matches_wide_where_exact() {
731        // Random pairs across the whole tracer domain (Q20.12 points up to
732        // +-2^24 against Q3.12 normals up to +-4096) plus the classic edges;
733        // wherever the exact product fits in i32 both forms agree bit for bit.
734        let mut state = 0x9e37_79b9_7f4a_7c15u64;
735        let mut next = || {
736            state ^= state << 13;
737            state ^= state >> 7;
738            state ^= state << 17;
739            state
740        };
741        let mut checked = 0u32;
742        for _ in 0..200_000 {
743            let r = next();
744            let value = ((r as i32) >> 7).clamp(-(1 << 24), 1 << 24);
745            let q12 = (((r >> 32) as i32) >> 19).clamp(-4096, 4096);
746            let exact = (i64::from(value) * i64::from(q12)) >> 12;
747            if exact > i64::from(i32::MAX) || exact < i64::from(i32::MIN) {
748                continue;
749            }
750            assert_eq!(
751                mul_q12_i32(value, q12),
752                mul_q12_i32_wide(value, q12),
753                "{value} {q12}"
754            );
755            assert_eq!(mul_q12_i32_wide(value, q12), exact as i32);
756            checked += 1;
757        }
758        assert!(checked > 190_000, "{checked}");
759        for &(value, q12) in &[
760            (0, 0),
761            (-1, 4096),
762            (-4095, 4096),
763            (4095, -4096),
764            (1 << 24, 4096),
765            (-(1 << 24), 4096),
766            (i32::MAX >> 12, 4096),
767            (i32::MIN >> 12, 4096),
768            (7, -1),
769            (-7, 1),
770        ] {
771            assert_eq!(
772                mul_q12_i32(value, q12),
773                mul_q12_i32_wide(value, q12),
774                "{value} {q12}"
775            );
776        }
777    }
778
779    fn xorshift(mut state: u64) -> impl FnMut() -> u64 {
780        move || {
781            state ^= state << 13;
782            state ^= state >> 7;
783            state ^= state << 17;
784            state
785        }
786    }
787
788    /// Exact floor root by definition: r^2 <= n < (r + 1)^2, in u128.
789    fn assert_floor_root(n: u64) {
790        let r = u128::from(isqrt_u64(n));
791        let n128 = u128::from(n);
792        assert!(
793            r * r <= n128 && (r + 1) * (r + 1) > n128,
794            "isqrt_u64({n}) = {r}"
795        );
796    }
797
798    /// The nibble root this replaced, below 2^48: the two must agree.
799    #[test]
800    #[cfg_attr(
801        miri,
802        ignore = "exhaustive sweep, minutes under Miri; make test runs it natively"
803    )]
804    fn isqrt_u64_matches_the_nibble_root_below_2_48() {
805        let mut next = xorshift(0x9e37_79b9_7f4a_7c15);
806        for i in 0..4_000_000u64 {
807            let n = (next() >> 16) >> (i % 48);
808            let old = isqrt_below_2_48((n >> 32) as u32, n as u32);
809            assert_eq!(isqrt_u64(n), old, "{n}");
810        }
811        for n in 0..1u64 << 22 {
812            assert_eq!(isqrt_u64(n), isqrt_below_2_48(0, n as u32), "{n}");
813        }
814    }
815
816    #[test]
817    #[cfg_attr(
818        miri,
819        ignore = "exhaustive sweep, minutes under Miri; make test runs it natively"
820    )]
821    fn isqrt_u64_is_exact_at_every_path_boundary() {
822        // Squares either side of the table seed, the 2^48 fast-path limit,
823        // the clamped Newton start (seed 2^24 - 1) and the top of the domain.
824        let roots = (0u64..300)
825            .chain((1 << 16) - 300..(1 << 16) + 300)
826            .chain((1 << 24) - 300..(1 << 24) + 300)
827            .chain((1 << 32) - 70_000..1 << 32);
828        for root in roots {
829            let square = root * root;
830            for n in [square.saturating_sub(1), square, square.saturating_add(1)] {
831                assert_floor_root(n);
832            }
833            assert_floor_root(square.saturating_add(2 * root));
834        }
835        for n in ((1u64 << 48) - 100_000..(1 << 48) + 100_000).chain(u64::MAX - 100_000..=u64::MAX)
836        {
837            assert_floor_root(n);
838        }
839        for n in 0..1 << 20 {
840            assert_floor_root(n);
841        }
842    }
843
844    #[test]
845    #[cfg_attr(
846        miri,
847        ignore = "exhaustive sweep, minutes under Miri; make test runs it natively"
848    )]
849    fn isqrt_u64_matches_definition_at_every_magnitude() {
850        let mut next = xorshift(0x2545_f491_4f6c_dd1d);
851        for i in 0..2_000_000u64 {
852            let n = next() >> (i % 64);
853            assert_floor_root(n);
854            // Perfect squares and their neighbours at the same magnitude.
855            let r = u64::from(isqrt_u64(n));
856            assert_floor_root((r * r).wrapping_sub(1));
857        }
858    }
859
860    #[test]
861    #[cfg_attr(
862        miri,
863        ignore = "exhaustive sweep, minutes under Miri; make test runs it natively"
864    )]
865    fn div_u64_by_u32_every_short_divisor_and_digit_boundary() {
866        let check = |n: u64, d: u32| {
867            assert_eq!(
868                u64::from(div_u64_by_u32((n >> 32) as u32, n as u32, d)),
869                n / u64::from(d),
870                "{n} / {d}"
871            );
872        };
873        let wide = [
874            65537,
875            0x7fff_ffff,
876            0x8000_0000,
877            0x8000_ffff,
878            0xffff_0001,
879            u32::MAX,
880        ];
881        for d in (1u32..=65536).chain(wide) {
882            for q in [
883                0u32,
884                1,
885                65534,
886                65535,
887                65536,
888                65537,
889                0x7fff_ffff,
890                0xffff_fffe,
891                u32::MAX,
892            ] {
893                for rem in [0, d / 2, d - 1] {
894                    check(u64::from(q) * u64::from(d) + u64::from(rem), d);
895                }
896            }
897        }
898        let mut next = xorshift(0x9ab4_3f87_6546_bcad);
899        for i in 0..2_000_000u32 {
900            let seed = next();
901            let d = ((seed as u32) >> (i % 32)).max(1);
902            let hi = ((seed >> 32) as u32) % d;
903            check((u64::from(hi) << 32) | u64::from(next() as u32), d);
904        }
905    }
906
907    /// The expression every `mul_div_i32` caller used to write.
908    fn mul_div_wide(a: i32, b: i32, c: i32) -> i32 {
909        (i64::from(a) * i64::from(b) / i64::from(c)) as i32
910    }
911
912    const SIGNED_EDGES: [i32; 23] = [
913        i32::MIN,
914        i32::MIN + 1,
915        -(1 << 30) - 1,
916        -65537,
917        -65536,
918        -65535,
919        -4096,
920        -7,
921        -2,
922        -1,
923        0,
924        1,
925        2,
926        3,
927        7,
928        4096,
929        46341,
930        65535,
931        65536,
932        65537,
933        1 << 30,
934        i32::MAX - 1,
935        i32::MAX,
936    ];
937
938    #[test]
939    fn mul_div_i32_matches_i64_on_every_edge_triple() {
940        for a in SIGNED_EDGES {
941            for b in SIGNED_EDGES {
942                for c in SIGNED_EDGES.into_iter().filter(|&c| c != 0) {
943                    assert_eq!(
944                        mul_div_i32(a, b, c),
945                        mul_div_wide(a, b, c),
946                        "{a} * {b} / {c}"
947                    );
948                }
949            }
950        }
951        // Truncation toward zero, not floor; wrapping where i64 -> i32 wraps.
952        assert_eq!(mul_div_i32(-7, 1, 2), -3);
953        assert_eq!(mul_div_i32(7, -1, 2), -3);
954        assert_eq!(mul_div_i32(-7, -1, -2), -3);
955        assert_eq!(mul_div_i32(i32::MIN, 1, -1), i32::MIN);
956        assert_eq!(mul_div_i32(i32::MIN, i32::MIN, 1), 0);
957        assert_eq!(mul_div_i32(i32::MAX, i32::MAX, 1), 1);
958        assert_eq!(mul_div_i32(123_456, 789, 1), 123_456 * 789);
959    }
960
961    #[test]
962    #[cfg_attr(
963        miri,
964        ignore = "exhaustive sweep, minutes under Miri; make test runs it natively"
965    )]
966    fn mul_div_i32_matches_i64_on_random_inputs_at_every_magnitude() {
967        let mut next = xorshift(0x5817_9513_0bad_cafe);
968        for i in 0..2_000_000u32 {
969            let r = next();
970            let a = (r as i32) >> (i % 32);
971            let b = ((r >> 32) as i32) >> ((i / 32) % 32);
972            let c = ((next() as i32) >> (i % 31)) | 1;
973            assert_eq!(
974                mul_div_i32(a, b, c),
975                mul_div_wide(a, b, c),
976                "{a} * {b} / {c}"
977            );
978            // The Q16 multiply and divide shapes the games use.
979            assert_eq!(mul_div_i32(a, b, 65536), mul_div_wide(a, b, 65536));
980            assert_eq!(mul_div_i32(a, 65536, c), mul_div_wide(a, 65536, c));
981        }
982    }
983
984    #[test]
985    #[should_panic]
986    fn mul_div_i32_panics_on_zero_divisor_with_a_small_product() {
987        mul_div_i32(3, 4, core::hint::black_box(0));
988    }
989
990    #[test]
991    #[should_panic]
992    fn mul_div_i32_panics_on_zero_divisor_with_a_wide_product() {
993        mul_div_i32(i32::MAX, i32::MAX, core::hint::black_box(0));
994    }
995
996    #[test]
997    #[cfg_attr(
998        miri,
999        ignore = "exhaustive sweep, minutes under Miri; make test runs it natively"
1000    )]
1001    fn shift_multiplies_match_i64_for_every_shift() {
1002        let check = |a: i32, b: i32, shift: u32| {
1003            let product = i64::from(a) * i64::from(b);
1004            assert_eq!(
1005                mul_shr_i32(a, b, shift),
1006                (product >> shift) as i32,
1007                "{a} {b} {shift}"
1008            );
1009            assert_eq!(
1010                mul_shr_trunc_i32(a, b, shift),
1011                (product / (1i64 << shift)) as i32,
1012                "{a} {b} {shift}"
1013            );
1014            if shift < 31 {
1015                assert_eq!(
1016                    mul_shr_trunc_i32(a, b, shift),
1017                    mul_div_i32(a, b, 1 << shift)
1018                );
1019            }
1020        };
1021        for shift in 0..32 {
1022            for a in SIGNED_EDGES {
1023                for b in SIGNED_EDGES {
1024                    check(a, b, shift);
1025                }
1026            }
1027        }
1028        let mut next = xorshift(0x3211_8a53_dead_beef);
1029        for i in 0..1_000_000u32 {
1030            let r = next();
1031            check(
1032                r as i32 >> (i % 32),
1033                (r >> 32) as i32 >> ((i / 7) % 32),
1034                i % 32,
1035            );
1036        }
1037        assert_eq!(mul_shr_i32(-1, 1, 16), -1);
1038        assert_eq!(mul_shr_trunc_i32(-1, 1, 16), 0);
1039        assert_eq!(mul_shr_i32(-65537, 1, 16), -2);
1040        assert_eq!(mul_shr_trunc_i32(-65537, 1, 16), -1);
1041    }
1042
1043    #[test]
1044    fn abs_saturates_at_min() {
1045        assert_eq!(abs_i32(i32::MIN), i32::MAX);
1046        assert_eq!(abs_i32(-5), 5);
1047        assert_eq!(abs_i32(7), 7);
1048        assert_eq!(abs_i16(i16::MIN), i16::MAX);
1049        assert_eq!(abs_i16(-5), 5);
1050    }
1051
1052    #[test]
1053    fn clamp_i16_covers_both_ends() {
1054        assert_eq!(clamp_i16(40_000), i16::MAX);
1055        assert_eq!(clamp_i16(-40_000), i16::MIN);
1056        assert_eq!(clamp_i16(123), 123);
1057    }
1058
1059    #[test]
1060    fn square_saturates_past_46340() {
1061        assert_eq!(square_i32_saturating(46_340), 46_340 * 46_340);
1062        assert_eq!(square_i32_saturating(46_341), i32::MAX);
1063        assert_eq!(square_i32_saturating(i32::MIN), i32::MAX);
1064        assert_eq!(square_i32_saturating(-3), 9);
1065    }
1066
1067    #[test]
1068    fn isqrt_matches_floor_sqrt() {
1069        assert_eq!(isqrt_i32(0), 0);
1070        assert_eq!(isqrt_i32(-9), 0);
1071        assert_eq!(isqrt_i32(1), 1);
1072        assert_eq!(isqrt_i32(15), 3);
1073        assert_eq!(isqrt_i32(16), 4);
1074        assert_eq!(isqrt_i32(i32::MAX), 46_340);
1075    }
1076
1077    #[test]
1078    fn mul_q12_handles_sign_and_scale() {
1079        assert_eq!(mul_q12_i32(1000, 4096), 1000);
1080        assert_eq!(mul_q12_i32(1000, 2048), 500);
1081        assert_eq!(mul_q12_i32(-1000, 2048), -500);
1082        assert_eq!(mul_q12_i32(0, 4096), 0);
1083    }
1084}