Skip to content

Add wide division: div_rem_wide and wrapping_div_wide - #1329

Open
cong-or wants to merge 2 commits 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 2 commits 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.69110% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.19%. Comparing base (c1b3419) to head (0ac5d1c).
⚠️ Report is 2 commits behind head on master.

Files with missing lines Patch % Lines
src/uint/ref_type/div.rs 97.52% 5 Missing ⚠️
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.
📢 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 Outdated
Comment thread src/uint/ref_type/div.rs
Comment thread src/uint/div.rs Outdated
Comment thread src/uint/div.rs Outdated
Comment thread src/uint/div.rs
Comment thread src/uint/div.rs Outdated
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.
@cong-or

cong-or commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

@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.

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