Add wide division: div_rem_wide and wrapping_div_wide - #1329
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1329 +/- ##
==========================================
+ Coverage 91.06% 91.19% +0.12%
==========================================
Files 189 189
Lines 22654 23036 +382
==========================================
+ Hits 20630 21007 +377
- Misses 2024 2029 +5 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
edb753c to
8d7d928
Compare
Make the wide-division methods clearer and safer to use: - Rename div_rem_wide to wrapping_div_rem_wide so the name warns that the quotient can be cut short when it is too big to fit. - Also hand back a yes/no flag saying whether the quotient fit without being cut short. - Fix exact division: a zero remainder on its own is not enough, the quotient has to fit too. For example 2^128 divided by 1 now correctly reports "no exact answer" instead of returning a wrong value. - Reuse the existing limb-shift helper instead of a near-copy of it. - Tidy the tests: clearer name for the checker and simpler random divisors.
|
@andrewwhitehead Thanks for the review! Pushed 0ac5d1c with everything addressed. Names. Renamed div_rem_wide → wrapping_div_rem_wide (and the vartime version) to make it clear the quotient may be truncated. They now also return a fits flag. The check is just hi < rhs, so it's simple, constant-time, and done before any state is modified. div_wide_exact. Good catch. A zero remainder wasn't sufficient: the quotient can still overflow (e.g. 2^128 / 1), and the old code returned the wrong value in that case. It now only returns Some when the remainder is zero and the quotient fits. I also fixed the docs and added a regression test. I left it built on the full division for now, since the faster path can be swapped in later. shift_in_limb. Reworked it to call conditional_shl_assign_by_limbs_vartime and then insert the new limb, so the shifting logic isn't duplicated. Tests. Renamed check → check_wide_division, dropped the nz helper in favor of NonZero::random_from_rng, and updated the checker to verify the new fits flag. One question: I currently have wrapping_div_rem_wide returning (quo, rem, fits). If you'd rather keep the API smaller, I'm happy to leave it as (quo, rem) and only expose the flag through div_wide_exact. |
Title
Add wide division:
div_rem_wideandwrapping_div_wideCloses #1315.
What this adds
Division for a double-width dividend — a number twice as wide as the
Uinttype, passed as a pair of halves(lo, hi)meaninglo + hi * 2^BITS, divided by a normal-width divisor.Both constant-time and variable-time forms, mirroring the existing division methods on
Uint:A quick example:
Why
rem_widealready lets you take the remainder of a double-width value without widening the type. This fills the obvious gap: getting the quotient of that same double-width value.The workaround today is to glue the two halves together with
concat()and divide the result. That has two downsides:concat()pushes you up to the nextUintsize, which only exists for certain sizes and means allocating a bigger type than you actually need (e.g. a ~5000-bit value forces you all the way toU8192).Concattrait is implemented.div_rem_widesidesteps both—it divides the(lo, hi)pair directly, at any limb count, with no wider type required. It's the natural quotient-returning companion torem_wide.Because the true quotient of a double-width dividend can itself be wider than the type,
wrapping_div_widekeeps the low half (reduces mod2^BITS), hence thewrapping_name, consistent with the existingwrapping_div.div_wide_exactis the wide version ofdiv_exact, for when you know the division comes out clean (in which case the quotient always fits).How it works
No new division algorithm—it reuses the existing engine.
The core is modeled directly on
rem_wide/rem_wide_large_shifted: the same Knuth long division with thediv3by2fast path and the same normalization. The one addition is that instead of throwing the quotient digits away, each digit (produced most-significant-first) is shifted into a small accumulator, yielding the low half of the quotient. Single-limb divisors take the simplerdiv2by1path, exactly likediv_rem.The variable-time form mirrors
rem_wide_vartime(trimming the divisor and stopping early), sharing the same core loop behind aconst VARTIMEflag.The implementation is a handful of small functions that parallel the existing
rem_widefamily, so it should read familiarly alongside the surrounding code.Testing
The new code is checked against a deliberately simple, trusted reference: glue the halves with
concat(), run the existing (well-tested)div_rem, and compare the quotient and remainder—for both the constant-time and variable-time methods.This runs over:
U64,U128,U192, andU256(a few thousand cases per run).All 561 existing library tests still pass, and
clippyandrustfmtare clean.One thing I'd like your call on
lo,hi, andrhs, matchingrem_wide. If you'd also like mixed-width generics (a widerhi/ different-widthrhs, as sketched in the issue), I'm happy to follow up with that separately.