Skip to main content

thruput_u256/
div.rs

1//! Division and remainder.
2//!
3//! [`U256::div_rem_small`] is a separate function rather than a fast path
4//! inside [`U256::div_rem`], so a contract that only divides by constants (or
5//! formats a decimal) links the cheap routine and not Knuth D. Measured by
6//! `scripts/bigint-bench.sh -b u256 -o "div div_small"`: linking `div` adds
7//! 2,572 bytes of rv32 .text where `div_small` adds 680, so the general path
8//! costs 1,892 on top of the small one. (It was 240 more before the Knuth D
9//! loop called `div_rem_2by1_normalized` directly, and far worse when the
10//! quotient estimate went through `__udivdi3` -- see the estimate below.)
11//!
12//! Knuth D is written once as a macro and instantiated at two dividend widths
13//! -- eight limbs for the `U256 / U256` operators, sixteen for the
14//! full-precision `mul_div` family, whose dividend is a 512-bit product. A
15//! single shared function over the wider buffers was measured and rejected;
16//! [`knuth_d`] has the numbers.
17
18use crate::{LIMBS, U256};
19use core::mem::MaybeUninit;
20
21/// The number of 32-bit limbs in a full-precision (512-bit) product.
22pub(crate) const WIDE_LIMBS: usize = 2 * LIMBS;
23
24/// The base of the limb representation, as a `u64` for intermediate work.
25const B: u64 = 1 << 32;
26
27/// Half-limb base, for the 64-by-32 divide below.
28const HB: u32 = 1 << 16;
29
30/// Divides `(hi, lo)` by `d`. Requires `hi < d` and `d != 0`; both hold at
31/// every call site.
32///
33/// Hacker's Delight's `divlu` -- schoolbook base 2^16, so every divide is a
34/// native 32-bit `DIVU`. The obvious `(hi:lo as u64) / d` spelling is not:
35/// `DIVU` is XLEN-wide, so on rv32 it lowers to `__udivdi3`/`__umoddi3`, one
36/// libcall per limb. (Verified by disassembly; it emitted zero `divu`.)
37#[inline]
38const fn div_rem_2by1(hi: u32, lo: u32, d: u32) -> (u32, u32) {
39    debug_assert!(hi < d);
40
41    // Normalise so the divisor's high bit is set, which is what bounds the
42    // half-digit estimates in the core.
43    let s = d.leading_zeros();
44    let un32 = if s == 0 {
45        hi
46    } else {
47        (hi << s) | (lo >> (32 - s))
48    };
49    let (q, rem) = div_rem_2by1_normalized(un32, lo << s, d << s);
50    (q, rem >> s)
51}
52
53/// The core of [`div_rem_2by1`], for a divisor whose high bit is already set.
54/// Requires `hi < d`.
55///
56/// Split out so the Knuth D loop, which normalises its divisor once up front,
57/// does not re-derive `s == 0` per quotient digit. LLVM cannot prove it --
58/// known-bits does not carry `x << clz(x)` through the array stores -- so the
59/// wrapper's selects and shifts would otherwise ride along in the hot loop as
60/// dead work.
61#[inline]
62const fn div_rem_2by1_normalized(hi: u32, lo: u32, d: u32) -> (u32, u32) {
63    debug_assert!(hi < d);
64    debug_assert!(d >> 31 == 1);
65
66    let vn1 = d >> 16;
67    let vn0 = d & 0xffff;
68
69    let un1 = lo >> 16;
70    let un0 = lo & 0xffff;
71
72    // `rhat < 2^16` is held by the `break`, and `q1 * vn0` is only reached once
73    // `q1 < 2^16`, so neither overflows.
74    let mut q1 = hi / vn1;
75    let mut rhat = hi - q1 * vn1;
76    while q1 >= HB || q1 * vn0 > (rhat << 16) | un1 {
77        q1 -= 1;
78        rhat += vn1;
79        if rhat >= HB {
80            break;
81        }
82    }
83
84    // `un21` fits in 32 bits but `hi * 2^16` does not; the wrapping is
85    // deliberate and lands on the right answer.
86    let un21 = hi
87        .wrapping_mul(HB)
88        .wrapping_add(un1)
89        .wrapping_sub(q1.wrapping_mul(d));
90
91    let mut q0 = un21 / vn1;
92    rhat = un21 - q0 * vn1;
93    while q0 >= HB || q0 * vn0 > (rhat << 16) | un0 {
94        q0 -= 1;
95        rhat += vn1;
96        if rhat >= HB {
97            break;
98        }
99    }
100
101    let rem = un21
102        .wrapping_mul(HB)
103        .wrapping_add(un0)
104        .wrapping_sub(q0.wrapping_mul(d));
105
106    (q1 * HB + q0, rem)
107}
108
109impl U256 {
110    /// Divides by a 32-bit value, returning the quotient and remainder.
111    ///
112    /// # Panics
113    ///
114    /// If `rhs` is zero.
115    #[inline]
116    pub const fn div_rem_small(self, rhs: u32) -> (Self, u32) {
117        self.div_rem_small_from(rhs, 0)
118    }
119
120    /// [`div_rem_small`](Self::div_rem_small), resuming from a partial
121    /// remainder rather than from zero.
122    ///
123    /// Chaining two calls divides a 512-bit value by a 32-bit one: the high
124    /// half's remainder seeds the low half. That keeps the `dn == 1` case of
125    /// [`div_rem_512`] on this cheap routine instead of widening the Knuth D
126    /// buffers to reach it.
127    ///
128    /// # Panics
129    ///
130    /// If `rhs` is zero. `rem` must already be below `rhs`, which every
131    /// caller satisfies by construction (it is a remainder of `rhs`).
132    #[inline]
133    pub(crate) const fn div_rem_small_from(
134        self,
135        rhs: u32,
136        rem: u32,
137    ) -> (Self, u32) {
138        if rhs == 0 {
139            panic!("attempt to divide by zero");
140        }
141        debug_assert!(rem < rhs);
142
143        let mut q = [0u32; LIMBS];
144        let mut rem = rem;
145
146        let mut i = LIMBS;
147        while i > 0 {
148            i -= 1;
149            // `rem` is a remainder, so `rem < rhs` holds on every iteration,
150            // which is exactly `div_rem_2by1`'s precondition.
151            let (qi, r) = div_rem_2by1(rem, self.limbs[i], rhs);
152            q[i] = qi;
153            rem = r;
154        }
155
156        (Self::from_limbs(q), rem)
157    }
158
159    /// Divides by another `U256`, returning the quotient and remainder.
160    ///
161    /// # Panics
162    ///
163    /// If `rhs` is zero, matching the primitive integers (which panic on a
164    /// zero divisor regardless of `overflow-checks`).
165    pub const fn div_rem(self, rhs: Self) -> (Self, Self) {
166        match self.checked_div_rem(rhs) {
167            Some(pair) => pair,
168            None => panic!("attempt to divide by zero"),
169        }
170    }
171
172    /// Divides by another `U256`, returning `None` if `rhs` is zero.
173    pub const fn checked_div_rem(self, rhs: Self) -> Option<(Self, Self)> {
174        let dn = rhs.limb_len();
175        if dn == 0 {
176            return None;
177        }
178        let nn = self.limb_len();
179
180        // Divisor larger than the dividend: quotient zero, remainder self.
181        if nn < dn {
182            return Some((Self::ZERO, self));
183        }
184
185        // Separate function, not an inlined copy, so a contract using only
186        // `div_rem_small` never links the code below.
187        if dn == 1 {
188            let (q, r) = self.div_rem_small(rhs.limbs[0]);
189            return Some((q, Self::from_u32(r)));
190        }
191
192        let (q, r) = div_rem_knuth(&self.limbs, nn, &rhs.limbs, dn);
193        Some((Self::from_limbs(q), r))
194    }
195    /// Division, returning `None` if `rhs` is zero.
196    #[inline]
197    pub const fn checked_div(self, rhs: Self) -> Option<Self> {
198        match self.checked_div_rem(rhs) {
199            Some((q, _)) => Some(q),
200            None => None,
201        }
202    }
203
204    /// Remainder, returning `None` if `rhs` is zero.
205    #[inline]
206    pub const fn checked_rem(self, rhs: Self) -> Option<Self> {
207        match self.checked_div_rem(rhs) {
208            Some((_, r)) => Some(r),
209            None => None,
210        }
211    }
212}
213
214/// The low [`LIMBS`] limbs of a full-precision limb array.
215#[inline]
216pub(crate) const fn low_limbs(wide: &[u32; WIDE_LIMBS]) -> [u32; LIMBS] {
217    let mut out = [0u32; LIMBS];
218    let mut i = 0;
219    while i < LIMBS {
220        out[i] = wide[i];
221        i += 1;
222    }
223    out
224}
225
226/// Generates a Knuth algorithm D (Hacker's Delight `divmnu`) specialised to a
227/// dividend of `$n` limbs, base 2^32.
228///
229/// The generated function divides the `nn`-limb value `u` by the `dn`-limb
230/// value `v`, returning the quotient's limbs and the remainder. The quotient
231/// occupies the low `nn - dn + 1` limbs of the returned array; the rest are
232/// zero.
233///
234/// It requires `2 <= dn <= LIMBS`, `dn <= nn <= $n`, `u[nn - 1] != 0` and
235/// `v[dn - 1] != 0`. Every caller establishes these from a `limb_len`.
236///
237/// # Why a macro
238///
239/// Two widths need this: the `U256 / U256` operators divide eight limbs, and
240/// [`div_rem_512`] divides the sixteen of a full-precision product. One body,
241/// two instantiations, each sized to its own dividend.
242///
243/// The obvious alternative -- one function over the wider buffers, called by
244/// both, since every loop is bounded by the runtime `nn`/`dn` anyway -- was
245/// written first and measured second. It loses.
246/// `scripts/bigint-bench.sh -b u256 -o "div div_small mul_div_u128"`, rv32,
247/// marginal over the `none` calibration build:
248///
249/// | | `div` .text | `div` gas | `mul_div_u128` .text | `mul_div_u128` gas |
250/// |---|---|---|---|---|
251/// | before this change | 2,640 | 963.05 | 3,036 | 1735.05 |
252/// | one shared function | 2,576 | 1221.05 | 2,996 | 1798.05 |
253/// | two instantiations | 2,572 | 972.05 | 3,012 | 1712.05 |
254///
255/// Sharing one function buys 64 bytes and costs **+258 gas per division, 27% on
256/// the bench's operands**. Division is the hot path of the contracts this
257/// crate exists for, so that is the wrong way round.
258/// Specialising gets the bytes anyway (68 below the pre-change baseline) for
259/// +0.9% gas, and leaves `mul_div_u128` both smaller and faster than before.
260///
261/// Read the 27% as a worst case, not a constant. The overhead is per call, so
262/// it is a larger fraction of a shallow division than a deep one, and the
263/// bench's operands make `div` shallow: its divisor is 201 bits, seven of
264/// eight limbs, so Knuth D runs its quotient loop twice out of a possible
265/// seven. It is also not purely fixed -- the same change cost `mul_div_u128`
266/// only 63 gas through the same `checked_div_rem`. Why the two call shapes
267/// differ by that much was not isolated; only the totals above were measured,
268/// and the mechanism is a guess this comment declines to make. What holds
269/// across every row is the sign: specialised is never worse than shared.
270///
271/// # The scratch buffers
272///
273/// Four arrays, and only two of them want zeros:
274///
275/// - `q` and `r` **are** the answer. The quotient occupies `nn - dn + 1` limbs
276///   and the remainder `dn`, but both are returned full width, so their high
277///   limbs genuinely are zero and something has to write them. They stay
278///   `[u32; _]`, zero-initialised.
279/// - `vn` and `un` are scratch. The normalisation loops fill `vn[0..dn]` and
280///   `un[0..=nn]` before anything reads them, and nothing ever reads above
281///   that, so zeroing them was dead work. They are `MaybeUninit` -- the
282///   `SAFETY` note above the function states the two invariants every read
283///   leans on.
284///
285/// LLVM could not remove that dead half on its own. The dynamic `vn[dn - 1]`
286/// and `un[j + dn]` indexing pins both arrays to the stack, out of reach of
287/// the dead-store elimination a scalarised array would have got, so the
288/// zeroing was real stores in the artifact: 40 `sw zero` over the `none`
289/// calibration build, which is all four arrays written out in full.
290///
291/// The safe way to reach them -- write every slot unconditionally, so a later
292/// store dominates each element -- was tried first, on `vn`, and does not
293/// work. `MaybeUninit` does. rv32, marginal over `none`:
294///
295/// | | `div` .text | `div` gas | `sw zero` |
296/// |---|---|---|---|
297/// | zero-init, then write `0..dn` | 2,572 | 972.05 | 40 |
298/// | write all `LIMBS` unconditionally | 2,924 | 947.05 | 40 |
299/// | `MaybeUninit` | **2,360** | **928.05** | **25** |
300///
301/// The full-width write keeps every zero store and costs 352 bytes to save 25
302/// gas -- backwards where the 64 KiB artifact cap binds. `MaybeUninit` removes
303/// 15 of the 40 and is smaller *and* faster on every caller:
304///
305/// | | .text | gas |
306/// |---|---|---|
307/// | `div` | 2,572 -> 2,360 (**-8.2%**) | 972.05 -> 928.05 (**-4.5%**) |
308/// | `mul_div_u128` | 3,012 -> 2,788 (**-7.4%**) | 1712.05 -> 1628.05 (**-4.9%**) |
309/// | `mul_div` | 3,992 -> 3,892 (**-2.5%**) | 4712.05 -> 4534.05 (**-3.8%**) |
310///
311/// This is the crate's only discretionary `unsafe`, so it is the one place
312/// Miri earns its keep: `.github/workflows/miri.yml` runs this crate's suite
313/// under it, and the tests thin their random sweeps under `cfg!(miri)` rather
314/// than skipping any shape.
315///
316/// Being a `const fn` was never the obstacle, incidentally -- `MaybeUninit`
317/// works in one on stable, compile-time evaluation of a partially initialised
318/// array included, so long as the uninitialised tail is never read.
319///
320/// `#[inline]` on the generated function was measured too, on the theory that
321/// it would restore the pre-split shape exactly: it costs 156 bytes on `div`
322/// to save 2 gas, and makes `mul_div_u128` both bigger and slower. Not taken.
323///
324/// What the split costs instead is now measured too, by an op that calls both
325/// paths in one kernel so the artifact carries both instantiations:
326///
327/// | | .text | gas |
328/// |---|---|---|
329/// | `mul_div` alone | 3,892 | 4534.05 |
330/// | `div` alone | 2,360 | 928.05 |
331/// | both in one artifact | 6,088 | 5468.05 |
332///
333/// So adding `/` to a contract that already links `mul_div` costs **2,196
334/// bytes** -- against 2,360 standalone, meaning barely 160 bytes are shared
335/// and the two Knuth D copies are all but fully duplicated. Sharing one
336/// function would have saved most of that ~2.2 KB, and cost 27% of every
337/// division's gas. Both halves of that trade are numbers.
338///
339/// (An earlier version of this comment guessed the second copy at "the 1,892
340/// bytes one copy costs over `div_small`". Measuring it said otherwise, by
341/// about 20% -- which is why it is a measurement now.)
342macro_rules! knuth_d {
343    ($name:ident, $n:expr) => {
344        // `vn` and `un` are `MaybeUninit` scratch, so every read of them is
345        // `unsafe`. Two facts, established here once, make all of those reads
346        // sound; the `SAFETY` comments below say which one each leans on.
347        //
348        // I1. `vn[0..dn]` is initialised. The normalisation loop writes every
349        //     index below `dn` before anything reads one, and every later read
350        //     is at `dn - 1`, at `dn - 2` (`dn >= 2`), or at an `i < dn`.
351        //
352        // I2. `un[0..=nn]` is initialised. Its loop writes `nn` and then every
353        //     index below it. Every later read is at `i + j`, `j + dn`,
354        //     `j + dn - 1`, `j + dn - 2` -- all at most `nn`, since `j` stops
355        //     at `nn - dn` and `i` at `dn - 1` -- or at `i` and `i + 1` in the
356        //     remainder loop, where `i < dn` and `dn <= nn`.
357        //
358        // Nothing reads above those bounds, which is the whole reason the
359        // tails are left uninitialised: see "Why the buffers are zeroed".
360        const fn $name(
361            u: &[u32; $n],
362            nn: usize,
363            v: &[u32; LIMBS],
364            dn: usize,
365        ) -> ([u32; $n], U256) {
366            debug_assert!(dn >= 2);
367            debug_assert!(nn >= dn);
368            debug_assert!(nn <= $n);
369
370            let s = v[dn - 1].leading_zeros();
371
372            // Normalise the divisor so its top limb has its high bit set, which is
373            // what bounds the quotient-digit estimate below. Establishes I1.
374            let mut vn: [MaybeUninit<u32>; LIMBS] =
375                [MaybeUninit::uninit(); LIMBS];
376            let mut i = dn;
377            while i > 0 {
378                i -= 1;
379                let lo = if i == 0 || s == 0 {
380                    0
381                } else {
382                    v[i - 1] >> (32 - s)
383                };
384                vn[i] = MaybeUninit::new((v[i] << s) | lo);
385            }
386
387            // Normalised dividend, one limb wider so the estimate always has two
388            // limbs to read. Establishes I2.
389            let mut un: [MaybeUninit<u32>; $n + 1] =
390                [MaybeUninit::uninit(); $n + 1];
391            un[nn] = MaybeUninit::new(if s == 0 {
392                0
393            } else {
394                u[nn - 1] >> (32 - s)
395            });
396            let mut i = nn;
397            while i > 0 {
398                i -= 1;
399                let lo = if i == 0 || s == 0 {
400                    0
401                } else {
402                    u[i - 1] >> (32 - s)
403                };
404                un[i] = MaybeUninit::new((u[i] << s) | lo);
405            }
406
407            let mut q = [0u32; $n];
408
409            let mut j = nn - dn + 1;
410            while j > 0 {
411                j -= 1;
412
413                // Estimate one quotient limb from the top two dividend limbs.
414                //
415                // `div_rem_2by1_normalized` wants `hi < dv`, while Knuth allows
416                // equality (the running remainder is below the divisor, so
417                // `hi <= dv`). Equality means the true digit is `B + lo / dv`, at
418                // least `B`; step D3 clamps that to `B - 1`, which is still an
419                // upper bound on the real quotient digit, and the correction below
420                // plus the add-back take it the rest of the way. Spelling the
421                // common branch as `div_rem_2by1_normalized` -- `dv` is the
422                // normalised divisor's top limb, so its high bit is set -- keeps
423                // the whole loop on native 32-bit `DIVU`s instead of one
424                // `__udivdi3` per quotient digit, and skips the wrapper's
425                // re-normalisation prologue.
426                // SAFETY: I2 -- `j + dn <= nn`, so both are initialised.
427                let hi = unsafe { un[j + dn].assume_init() };
428                let lo = unsafe { un[j + dn - 1].assume_init() };
429                // SAFETY: I1 -- `dn >= 2`, so `dn - 1 < dn`.
430                let dv = unsafe { vn[dn - 1].assume_init() };
431
432                let (mut qhat, mut rhat) = if hi < dv {
433                    let (qh, rh) = div_rem_2by1_normalized(hi, lo, dv);
434                    (qh as u64, rh as u64)
435                } else {
436                    // `hi <= dv` (above), so only equality reaches here, and the
437                    // Knuth rhat `num - (B - 1) * dv` collapses: `num` is
438                    // `dv * B + lo`, so subtracting `(B - 1) * dv` leaves
439                    // `dv + lo`.
440                    debug_assert!(hi == dv);
441                    (B - 1, (dv as u64) + (lo as u64))
442                };
443
444                // `rhat >= B` makes the test below vacuous -- `(rhat << 32) | ..`
445                // exceeds any `qhat * vn[dn - 2]` -- and the shift would drop the
446                // bits that make that true, so it guards the loop rather than only
447                // breaking out of it. `qhat < B` now holds by construction, so the
448                // estimate no longer needs its own bound check.
449                // SAFETY: I1 and I2 -- `dn - 2 < dn` (`dn >= 2`) and
450                // `j + dn - 2 < nn`.
451                let vn_second = unsafe { vn[dn - 2].assume_init() };
452                let un_third = unsafe { un[j + dn - 2].assume_init() };
453                while rhat < B
454                    && qhat * (vn_second as u64)
455                        > (rhat << 32) | (un_third as u64)
456                {
457                    qhat -= 1;
458                    rhat += dv as u64;
459                }
460
461                // Multiply and subtract.
462                let mut k: i64 = 0;
463                let mut i = 0;
464                while i < dn {
465                    // SAFETY: I1 -- `i < dn`.
466                    let vi = unsafe { vn[i].assume_init() };
467                    // SAFETY: I2 -- `i + j <= nn - 1`.
468                    let uij = unsafe { un[i + j].assume_init() };
469                    let p = qhat * (vi as u64);
470                    let t = (uij as i64) - k - ((p & 0xffff_ffff) as i64);
471                    un[i + j] = MaybeUninit::new(t as u32);
472                    k = ((p >> 32) as i64) - (t >> 32);
473                    i += 1;
474                }
475                // SAFETY: I2 -- `j + dn <= nn`.
476                let t = (unsafe { un[j + dn].assume_init() } as i64) - k;
477                un[j + dn] = MaybeUninit::new(t as u32);
478
479                q[j] = qhat as u32;
480
481                // Estimate one too large (probability ~2/2^32): add the divisor
482                // back. Rare enough that the tests need a wide sweep to hit it.
483                if t < 0 {
484                    q[j] -= 1;
485                    let mut carry = 0u64;
486                    let mut i = 0;
487                    while i < dn {
488                        // SAFETY: I1 and I2 -- `i < dn` and `i + j <= nn - 1`.
489                        let vi = unsafe { vn[i].assume_init() };
490                        let uij = unsafe { un[i + j].assume_init() };
491                        let sum = (uij as u64) + (vi as u64) + carry;
492                        un[i + j] = MaybeUninit::new(sum as u32);
493                        carry = sum >> 32;
494                        i += 1;
495                    }
496                    // SAFETY: I2 -- `j + dn <= nn`.
497                    let top = unsafe { un[j + dn].assume_init() };
498                    un[j + dn] = MaybeUninit::new(
499                        (top as u64).wrapping_add(carry) as u32,
500                    );
501                }
502            }
503
504            // Undo the normalisation shift on the remainder.
505            let mut r = [0u32; LIMBS];
506            let mut i = 0;
507            while i < dn {
508                // SAFETY: I2 -- `i < dn` and `dn <= nn`, so `i + 1 <= nn`.
509                let lo = unsafe { un[i].assume_init() };
510                let next = unsafe { un[i + 1].assume_init() };
511                let hi = if s == 0 { 0 } else { next << (32 - s) };
512                r[i] = (lo >> s) | hi;
513                i += 1;
514            }
515
516            (q, U256::from_limbs(r))
517        }
518    };
519}
520
521knuth_d!(div_rem_knuth, LIMBS);
522knuth_d!(div_rem_knuth_wide, WIDE_LIMBS);
523
524/// Divides the 512-bit value `hi * 2^256 + lo` by `d`, returning the
525/// quotient's sixteen limbs and the remainder. `None` if `d` is zero.
526///
527/// The quotient genuinely needs 512 bits when `d <= hi`; callers that require
528/// a `U256` answer check the upper half themselves. The remainder is below
529/// `d`, so it always fits.
530pub(crate) const fn div_rem_512(
531    hi: U256,
532    lo: U256,
533    d: U256,
534) -> Option<([u32; WIDE_LIMBS], U256)> {
535    let dn = d.limb_len();
536    if dn == 0 {
537        return None;
538    }
539
540    // Same split as the narrow path: dividing by a single limb stays on
541    // `div_rem_small`, seeded across the two halves, rather than widening
542    // Knuth D's buffers to reach a case it does not need.
543    if dn == 1 {
544        let divisor = d.limbs[0];
545        let (qhi, rem) = hi.div_rem_small(divisor);
546        let (qlo, rem) = lo.div_rem_small_from(divisor, rem);
547        let mut q = [0u32; WIDE_LIMBS];
548        let mut i = 0;
549        while i < LIMBS {
550            q[i] = qlo.limbs[i];
551            q[i + LIMBS] = qhi.limbs[i];
552            i += 1;
553        }
554        return Some((q, U256::from_u32(rem)));
555    }
556
557    let mut u = [0u32; WIDE_LIMBS];
558    let mut i = 0;
559    while i < LIMBS {
560        u[i] = lo.limbs[i];
561        u[i + LIMBS] = hi.limbs[i];
562        i += 1;
563    }
564
565    let mut nn = WIDE_LIMBS;
566    while nn > 0 && u[nn - 1] == 0 {
567        nn -= 1;
568    }
569    // `dn <= LIMBS`, so a dividend with fewer significant limbs than the
570    // divisor has an empty high half, and the remainder is just `lo`.
571    if nn < dn {
572        return Some(([0u32; WIDE_LIMBS], lo));
573    }
574
575    let (q, r) = div_rem_knuth_wide(&u, nn, &d.limbs, dn);
576    Some((q, r))
577}
578
579/// Computes `a * b / d` exactly, without the intermediate product needing to
580/// fit in 128 bits.
581///
582/// This is the case that sends contract authors looking for a 256-bit type in
583/// the first place: two 128-bit amounts multiplied and scaled back down. The
584/// `U256` never escapes into caller code.
585///
586/// # Panics
587///
588/// If `d` is zero, or if the quotient does not fit in a `u128`. The overflow
589/// panic is unconditional rather than `overflow-checks`-gated: this is an
590/// explicit API rather than an operator, and silently wrapping the result of a
591/// deliberate exact-arithmetic call has no primitive behaviour to match.
592#[inline]
593pub fn mul_div_u128(a: u128, b: u128, d: u128) -> u128 {
594    match checked_mul_div_u128(a, b, d) {
595        Some(v) => v,
596        None if d == 0 => panic!("attempt to divide by zero"),
597        None => panic!("mul_div_u128 quotient exceeds u128"),
598    }
599}
600
601/// Computes `a * b / d` exactly, returning `None` if `d` is zero or the
602/// quotient does not fit in a `u128`.
603#[inline]
604pub fn checked_mul_div_u128(a: u128, b: u128, d: u128) -> Option<u128> {
605    let (q, _) =
606        U256::widening_mul_u128(a, b).checked_div_rem(U256::from(d))?;
607    u128::try_from(q).ok()
608}
609
610#[cfg(test)]
611mod tests {
612    use super::*;
613
614    /// The spelling `div_rem_2by1` replaced -- correct and obvious, just a
615    /// libcall on rv32.
616    fn reference(hi: u32, lo: u32, d: u32) -> (u32, u32) {
617        let n = ((hi as u64) << 32) | (lo as u64);
618        ((n / d as u64) as u32, (n % d as u64) as u32)
619    }
620
621    fn check(hi: u32, lo: u32, d: u32) {
622        assert!(hi < d, "precondition");
623        assert_eq!(
624            div_rem_2by1(hi, lo, d),
625            reference(hi, lo, d),
626            "({hi}, {lo}) / {d}"
627        );
628        // A normalised divisor also satisfies the core's stricter
629        // precondition, so pin the core against the oracle directly rather
630        // than only through the wrapper's `s == 0` path.
631        if d >> 31 == 1 {
632            assert_eq!(
633                div_rem_2by1_normalized(hi, lo, d),
634                reference(hi, lo, d),
635                "normalized ({hi}, {lo}) / {d}"
636            );
637        }
638    }
639
640    #[test]
641    fn div_rem_2by1_matches_u64_on_boundaries() {
642        let interesting = [
643            1u32,
644            2,
645            3,
646            10,
647            1_000_000_000,
648            0xffff,
649            0x1_0000,
650            0x8000_0000,
651            0xffff_fffe,
652            u32::MAX,
653        ];
654        let los = [0u32, 1, 0xffff, 0x1_0000, 0x8000_0000, u32::MAX];
655
656        for d in interesting {
657            for lo in los {
658                // hi must stay below d.
659                for hi in [0, d / 2, d.saturating_sub(1)] {
660                    if hi < d {
661                        check(hi, lo, d);
662                    }
663                }
664            }
665        }
666    }
667
668    #[test]
669    fn div_rem_2by1_matches_u64_on_random_inputs() {
670        // Deterministic xorshift so a failure reproduces exactly.
671        let mut x = 0x2545_f491_4f6c_dd1du64;
672        let mut next = move || {
673            x ^= x << 13;
674            x ^= x >> 7;
675            x ^= x << 17;
676            x
677        };
678
679        for _ in 0..200_000 {
680            let d = (next() as u32).max(1);
681            let hi = (next() as u32) % d;
682            let lo = next() as u32;
683            check(hi, lo, d);
684        }
685    }
686
687    /// Divisors with a set high bit skip normalisation (`s == 0`), which is
688    /// the branch where a `>> (32 - s)` would be undefined.
689    #[test]
690    fn div_rem_2by1_handles_unnormalised_and_normalised_divisors() {
691        let mut x = 0x9e37_79b9_7f4a_7c15u64;
692        let mut next = move || {
693            x ^= x << 13;
694            x ^= x >> 7;
695            x ^= x << 17;
696            x
697        };
698
699        for _ in 0..50_000 {
700            // Already normalised: high bit set, so s == 0.
701            let d = (next() as u32) | 0x8000_0000;
702            check((next() as u32) % d, next() as u32, d);
703
704            // Maximally unnormalised: s == 31.
705            check(0, next() as u32, 1);
706        }
707    }
708}