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.17% +0.10%
==========================================
Files 189 189
Lines 22654 22995 +341
==========================================
+ Hits 20630 20965 +335
- Misses 2024 2030 +6 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
edb753c to
8d7d928
Compare
| /// # Panics | ||
| /// If the divisor is zero. | ||
| #[inline(always)] | ||
| pub(crate) const fn div_rem_wide( |
There was a problem hiding this comment.
It looks like this should be named wrapping_div_rem_wide for instance to make it clear that the quotient can be truncated. Can we maybe detect truncation and return a Choice indicating whether it's exact?
| /// Used as a quotient accumulator: feeding quotient limbs most-significant first retains the | ||
| /// low `self.nlimbs()` limbs of the full-width quotient. | ||
| #[inline(always)] | ||
| const fn shift_in_limb(&mut self, limb: Limb, shift: Choice) { |
There was a problem hiding this comment.
This seems to be duplicating conditional_shl_assign_by_limbs_vartime?
| check::<{ U64::LIMBS }, { U128::LIMBS }>( | ||
| lo64, | ||
| hi64, | ||
| nz(U64::random_from_rng(&mut rng)), |
There was a problem hiding this comment.
I think this should be able to use NonZero::<U64>::random_from_rng making the nz method unnecessary
| /// Check the wide-division methods (constant-time and variable-time) for a dividend | ||
| /// `lo + hi * 2^(L * Limb::BITS)` against the trusted `div_rem` reference, which divides the | ||
| /// same value widened into `Uint<W>` (with `W == 2 * L`). | ||
| fn check<const L: usize, const W: usize>(lo: Uint<L>, hi: Uint<L>, den: Uint<L>) { |
There was a problem hiding this comment.
This should probably be named check_wide_division or something less ambiguous
| /// ``` | ||
| #[inline] | ||
| #[must_use] | ||
| pub const fn div_wide_exact(lower_upper: (Self, Self), rhs: &NonZero<Self>) -> CtOption<Self> { |
There was a problem hiding this comment.
The nice thing about div_exact is that it is much faster than regular division, although this implementation could be switched later. I'm not sure I understand what is guaranteeing that the quotient actually fits since wrapping doesn't seem to be flagged?
| /// ``` | ||
| #[inline] | ||
| #[must_use] | ||
| pub const fn div_rem_wide(lower_upper: (Self, Self), rhs: &NonZero<Self>) -> (Self, Self) { |
There was a problem hiding this comment.
I think this would also need to be named wrapping_div_rem_wide or similar
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.