Skip to content

Commit d25b856

Browse files
committed
feat(ff): add div_rem to BigInteger
Add a div_rem method to the BigInteger trait returning the quotient and remainder of division by another BigInteger, so callers no longer need to detour through num_bigint::BigInt for division. The implementation generalizes the existing const_modulo base-2 long division to also accumulate the quotient. Tested against num_bigint as an oracle over 1000 random pairs per BigInteger width, plus edge cases. Closes #645.
1 parent af564e4 commit d25b856

3 files changed

Lines changed: 112 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
- (`ark-serialize`) Implementation of `CanonicalSerialize` and `CanonicalDeserialize` for signed integer types
2525
- [\#1084](https://github.com/arkworks-rs/algebra/pull/1084) (`ark-ff`) Add `from_u128` const constructor for `SmallFp` fields
2626
- [\#1086](https://github.com/arkworks-rs/algebra/pull/1086) (`ark-ff-macros`) Auto-detect small prime subgroup (bases 3, 5, 7) in `define_field!` for both `SmallFp` and `Fp` fields
27+
- (`ark-ff`) Add `div_rem` method for `BigInteger`, returning the quotient and remainder of division by another `BigInteger`.
2728

2829
### Improvements
2930

ff/src/biginteger/mod.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1130,6 +1130,73 @@ pub trait BigInteger:
11301130
/// ```
11311131
fn mul(&self, other: &Self) -> (Self, Self);
11321132

1133+
/// Divides this [`BigInteger`] by `divisor`, returning the quotient and the
1134+
/// remainder, i.e. the unique pair `(q, r)` such that
1135+
/// `self = q * divisor + r` with `r < divisor`.
1136+
///
1137+
/// # Panics
1138+
///
1139+
/// Panics if `divisor` is zero.
1140+
///
1141+
/// # Example
1142+
///
1143+
/// ```
1144+
/// use ark_ff::{biginteger::BigInteger64 as B, BigInteger as _};
1145+
///
1146+
/// // Basic
1147+
/// let a = B::from(100u64);
1148+
/// let b = B::from(7u64);
1149+
/// let (q, r) = a.div_rem(&b);
1150+
/// assert_eq!(q, B::from(14u64));
1151+
/// assert_eq!(r, B::from(2u64));
1152+
///
1153+
/// // Edge-Case: divisor larger than dividend
1154+
/// let a = B::from(3u64);
1155+
/// let b = B::from(5u64);
1156+
/// let (q, r) = a.div_rem(&b);
1157+
/// assert_eq!(q, B::from(0u64));
1158+
/// assert_eq!(r, B::from(3u64));
1159+
/// ```
1160+
///
1161+
/// Provided in terms of the other trait methods, so adding it does not
1162+
/// require existing implementors to supply a body.
1163+
fn div_rem(&self, divisor: &Self) -> (Self, Self) {
1164+
assert!(!divisor.is_zero(), "attempt to divide by zero");
1165+
1166+
// Base-2 long division: the same algorithm as the `const_modulo!`
1167+
// helper, extended to also accumulate the quotient. We scan the bits of
1168+
// `self` from the most significant down to the least, shifting each into
1169+
// a running remainder; whenever the remainder reaches `divisor` we
1170+
// subtract it and set the corresponding bit of the quotient.
1171+
let mut quotient = Self::from(0u64);
1172+
let mut remainder = Self::from(0u64);
1173+
1174+
for i in (0..self.num_bits() as usize).rev() {
1175+
// Make room for this iteration's quotient bit. `quotient` never
1176+
// exceeds `self`, so the discarded carry is always zero.
1177+
quotient.mul2();
1178+
1179+
// Shift the next bit of `self` into the remainder. `remainder` is
1180+
// always `< divisor` at the top of the loop, so doubling it can
1181+
// overflow `Self` by at most one bit, which `carry` captures.
1182+
let carry = remainder.mul2();
1183+
// After `mul2` the low bit is zero, so setting it is equivalent to
1184+
// OR-ing the next dividend bit in. Use the guaranteed `AsMut<[u64]>`
1185+
// view rather than a concrete field so this stays a default method.
1186+
if self.get_bit(i) {
1187+
remainder.as_mut()[0] |= 1;
1188+
}
1189+
1190+
if carry || remainder >= *divisor {
1191+
let borrow = remainder.sub_with_borrow(divisor);
1192+
debug_assert_eq!(borrow, carry);
1193+
quotient.as_mut()[0] |= 1;
1194+
}
1195+
}
1196+
1197+
(quotient, remainder)
1198+
}
1199+
11331200
/// Performs a rightwise bitshift of this number, effectively dividing
11341201
/// it by 2.
11351202
/// # Example

ff/src/biginteger/tests.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,49 @@ fn biginteger_conversion_test<B: BigInteger>() {
227227
assert_eq!(x, x_recovered);
228228
}
229229

230+
// Test `div_rem` against `num_bigint` as an oracle, plus hand-picked edge cases.
231+
fn biginteger_div_rem_test<B: BigInteger>() {
232+
let mut rng = ark_std::test_rng();
233+
234+
let zero = B::from(0u64);
235+
let one = B::from(1u64);
236+
237+
for _ in 0..1000 {
238+
let a: B = UniformRand::rand(&mut rng);
239+
let mut b: B = UniformRand::rand(&mut rng);
240+
if b.is_zero() {
241+
b = one;
242+
}
243+
244+
let (q, r) = a.div_rem(&b);
245+
246+
// The remainder is always strictly smaller than the divisor. Checked
247+
// before the `BigUint` conversions below so the test does not rely on
248+
// `B: Copy` to reuse `r` and `b`.
249+
assert!(r < b);
250+
251+
let a_big: BigUint = a.into();
252+
let b_big: BigUint = b.into();
253+
assert_eq!(Into::<BigUint>::into(q), &a_big / &b_big);
254+
assert_eq!(Into::<BigUint>::into(r), &a_big % &b_big);
255+
}
256+
257+
// 0 / d == (0, 0)
258+
assert_eq!(zero.div_rem(&one), (zero, zero));
259+
// n / n == (1, 0)
260+
let five = B::from(5u64);
261+
assert_eq!(five.div_rem(&five), (one, zero));
262+
// dividend < divisor == (0, dividend)
263+
assert_eq!(B::from(3u64).div_rem(&five), (zero, B::from(3u64)));
264+
// exact division by one
265+
assert_eq!(B::from(42u64).div_rem(&one), (B::from(42u64), zero));
266+
// basic case with a non-zero remainder
267+
assert_eq!(
268+
B::from(100u64).div_rem(&B::from(7u64)),
269+
(B::from(14u64), B::from(2u64))
270+
);
271+
}
272+
230273
// Wrapper test function for BigInteger
231274
fn test_biginteger<B: BigInteger>(max: B, zero: B) {
232275
let mut rng = ark_std::test_rng();
@@ -238,6 +281,7 @@ fn test_biginteger<B: BigInteger>(max: B, zero: B) {
238281
biginteger_bitwise_ops_test::<B>();
239282
biginteger_shr::<B>();
240283
biginteger_shl::<B>();
284+
biginteger_div_rem_test::<B>();
241285
}
242286

243287
#[test]

0 commit comments

Comments
 (0)