Skip to content

Add wide division: div_rem_wide and wrapping_div_wide - #1329

Open
cong-or wants to merge 1 commit into
RustCrypto:masterfrom
cong-or:div-wide-1315
Open

Add wide division: div_rem_wide and wrapping_div_wide#1329
cong-or wants to merge 1 commit into
RustCrypto:masterfrom
cong-or:div-wide-1315

Conversation

@cong-or

@cong-or cong-or commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Title

Add wide division: div_rem_wide and wrapping_div_wide

Closes #1315.

What this adds

Division for a double-width dividend — a number twice as wide as the Uint type, passed as a pair of halves (lo, hi) meaning lo + hi * 2^BITS, divided by a normal-width divisor.

Both constant-time and variable-time forms, mirroring the existing division methods on Uint:

// constant-time
Uint::div_rem_wide((lo, hi), rhs)            // -> (quotient, remainder)
Uint::wrapping_div_wide((lo, hi), rhs)       // -> quotient only
Uint::div_wide_exact((lo, hi), rhs)          // -> Some(quotient) if it divides evenly, else None

// variable-time in the divisor (constant-time for a fixed divisor)
Uint::div_rem_wide_vartime((lo, hi), rhs)
Uint::wrapping_div_wide_vartime((lo, hi), rhs)
Uint::div_wide_exact_vartime((lo, hi), rhs)

A quick example:

// dividend = 3 * 2^256 + 5, divided by 3
let (quo, rem) = U256::div_rem_wide((U256::from(5u64), U256::from(3u64)), &three);

// true quotient is 2^256 + 1, which doesn't fit in 256 bits, so it wraps to 1
assert_eq!(quo, U256::ONE);
assert_eq!(rem, U256::from(2u64));

Why

rem_wide already 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:

  1. concat() pushes you up to the next Uint size, 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 to U8192).
  2. It only works for types where the Concat trait is implemented.

div_rem_wide sidesteps both—it divides the (lo, hi) pair directly, at any limb count, with no wider type required. It's the natural quotient-returning companion to rem_wide.

Because the true quotient of a double-width dividend can itself be wider than the type, wrapping_div_wide keeps the low half (reduces mod 2^BITS), hence the wrapping_ name, consistent with the existing wrapping_div. div_wide_exact is the wide version of div_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 the div3by2 fast 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 simpler div2by1 path, exactly like div_rem.

The variable-time form mirrors rem_wide_vartime (trimming the divisor and stopping early), sharing the same core loop behind a const VARTIME flag.

The implementation is a handful of small functions that parallel the existing rem_wide family, 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:

  • Randomized inputs at U64, U128, U192, and U256 (a few thousand cases per run).
  • Full-width, single-word, and mid-width divisors, chosen so every internal branch is exercised (including the "divisor is narrower than the dividend" case and the variable-time early exit).
  • Hand-written edge cases: zero halves, divide-by-one, all-ones, and exact divisions.

All 561 existing library tests still pass, and clippy and rustfmt are clean.

One thing I'd like your call on

  • Scope. The methods use a uniform width for lo, hi, and rhs, matching rem_wide. If you'd also like mixed-width generics (a wider hi / different-width rhs, as sketched in the issue), I'm happy to follow up with that separately.

@codecov

codecov Bot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.24047% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.17%. Comparing base (c1b3419) to head (ab76d46).

Files with missing lines Patch % Lines
src/uint/ref_type/div.rs 97.44% 5 Missing ⚠️
src/uint/div.rs 99.31% 1 Missing ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@cong-or
cong-or force-pushed the div-wide-1315 branch 3 times, most recently from edb753c to 8d7d928 Compare July 28, 2026 10:38
@cong-or
cong-or marked this pull request as ready for review July 28, 2026 10:47
Comment thread src/uint/ref_type/div.rs
/// # Panics
/// If the divisor is zero.
#[inline(always)]
pub(crate) const fn div_rem_wide(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Comment thread src/uint/ref_type/div.rs
/// 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) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be duplicating conditional_shl_assign_by_limbs_vartime?

Comment thread src/uint/div.rs
check::<{ U64::LIMBS }, { U128::LIMBS }>(
lo64,
hi64,
nz(U64::random_from_rng(&mut rng)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be able to use NonZero::<U64>::random_from_rng making the nz method unnecessary

Comment thread src/uint/div.rs
/// 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>) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be named check_wide_division or something less ambiguous

Comment thread src/uint/div.rs
/// ```
#[inline]
#[must_use]
pub const fn div_wide_exact(lower_upper: (Self, Self), rhs: &NonZero<Self>) -> CtOption<Self> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Comment thread src/uint/div.rs
/// ```
#[inline]
#[must_use]
pub const fn div_rem_wide(lower_upper: (Self, Self), rhs: &NonZero<Self>) -> (Self, Self) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would also need to be named wrapping_div_rem_wide or similar

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Uint::wrapping_div_wide(_exact)

2 participants