Skip to content

Commit 2914f44

Browse files
Yu-zhbobzhang
authored andcommitted
fix(bigint): keep narrow quotients on Knuth division
1 parent b31a61c commit 2914f44

4 files changed

Lines changed: 70 additions & 6 deletions

File tree

bigint/bigint_wide_wbtest.mbt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,21 @@ test "BigInt::bit_length" {
183183
inspect((1N << 63).bit_length(), content="64")
184184
}
185185

186+
///|
187+
test "recursive division dispatch accounts for quotient width" {
188+
@debug.debug_inspect(
189+
[
190+
use_recursive_division(64, 64),
191+
use_recursive_division(65, 64),
192+
use_recursive_division(66, 64),
193+
use_recursive_division(128, 63),
194+
use_recursive_division(129, 128),
195+
use_recursive_division(130, 128),
196+
],
197+
content="[false, false, true, false, false, true]",
198+
)
199+
}
200+
186201
///|
187202
test "decimal sizing avoids Int overflow" {
188203
let cases : Array[Int64] = [

bigint/division_bench_test.mbt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,17 @@ fn make_unbalanced_division(
5151
(quotient * divisor + divisor - 1N, divisor)
5252
}
5353

54+
///|
55+
/// Builds the large-divisor, single-limb-quotient shape used by pidigits.
56+
fn make_narrow_division(
57+
quotient : Int,
58+
divisor_limbs : Int,
59+
) -> (@bigint.BigInt, @bigint.BigInt) {
60+
let divisor = division_bench_dense(divisor_limbs * 8, 0x8796a5b4c3d2e1f0UL)
61+
let quotient = @bigint.BigInt::from_int(quotient)
62+
(quotient * divisor + divisor - 1N, divisor)
63+
}
64+
5465
///|
5566
test "bench BigInt balanced division n=4000 digits" (it : @bench.T) {
5667
let (dividend, divisor) = make_balanced_division(4000)
@@ -74,3 +85,9 @@ test "bench BigInt unbalanced division q=65536 d=64 limbs" (it : @bench.T) {
7485
let (dividend, divisor) = make_unbalanced_division(65536, 64)
7586
it.bench(fn() { it.keep(dividend / divisor) })
7687
}
88+
89+
///|
90+
test "bench BigInt narrow division q=9 d=64 limbs" (it : @bench.T) {
91+
let (dividend, divisor) = make_narrow_division(9, 64)
92+
it.bench(fn() { it.keep(dividend / divisor) })
93+
}

bigint/division_wide.mbt

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,24 @@
2424
/// Divisors below this size are faster with ordinary Knuth D.
2525
const DIV_RECURSIVE_THRESHOLD = 64
2626

27+
///|
28+
/// Recursive division only pays off when both the divisor and quotient are
29+
/// wide. With at most two quotient-limb passes, Knuth D is linear in the large
30+
/// divisor and avoids recursive splitting, multiplication, and allocation.
31+
/// Requires `dividend_len >= divisor_len`.
32+
fn use_recursive_division(dividend_len : Int, divisor_len : Int) -> Bool {
33+
divisor_len >= DIV_RECURSIVE_THRESHOLD && dividend_len - divisor_len > 1
34+
}
35+
2736
///|
2837
/// Magnitude-only division. Both operands must be positive and `self > other`.
2938
fn BigInt::div_mod_mag(self : BigInt, other : BigInt) -> (BigInt, BigInt) {
3039
if other.len == 1 {
3140
self.div_mod_single_limb(other.limbs.unsafe_get(0))
32-
} else if other.len < DIV_RECURSIVE_THRESHOLD {
33-
self.knuth_div(other)
34-
} else {
41+
} else if use_recursive_division(self.len, other.len) {
3542
self.div_mod_recursive(other)
43+
} else {
44+
self.knuth_div(other)
3645
}
3746
}
3847

@@ -60,10 +69,10 @@ fn div_mod_normalized(u : BigInt, v : BigInt) -> (BigInt, BigInt) {
6069
if v.len == 1 {
6170
return u.div_mod_single_limb(v.limbs.unsafe_get(0))
6271
}
63-
if v.len < DIV_RECURSIVE_THRESHOLD {
64-
return u.knuth_div(v)
72+
if use_recursive_division(u.len, v.len) {
73+
return div_mod_recursive_steps(u, v)
6574
}
66-
div_mod_recursive_steps(u, v)
75+
u.knuth_div(v)
6776
}
6877

6978
///|

bigint/quickcheck_deep_test.mbt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,29 @@ test "recursive division with highly unbalanced operands" {
245245
}
246246
}
247247

248+
///|
249+
test "large divisors with narrow quotients" {
250+
// On native/wasm these sizes straddle and exceed the 64-limb recursive
251+
// divisor threshold. Their single-limb quotients must stay on Knuth D.
252+
let divisor_sizes : Array[Int] = [126, 128, 130, 256]
253+
let quotients : Array[@bigint.BigInt] = [
254+
2N,
255+
9N,
256+
(1N << 63) - 1N,
257+
(1N << 64) - 1N,
258+
]
259+
for size in divisor_sizes {
260+
let divisor = mag_of_limbs(seed_u64(size) + 0x51a11UL, size)
261+
for quotient in quotients {
262+
for remainder in ([0N, 1N, divisor >> 1, divisor - 1N] : Array[_]) {
263+
let dividend = quotient * divisor + remainder
264+
assert_eq(dividend / divisor, quotient)
265+
assert_eq(dividend % divisor, remainder)
266+
}
267+
}
268+
}
269+
}
270+
248271
///|
249272
test "division with adversarial divisor top limbs" {
250273
// Knuth 4.3.1 quotient-digit estimation is most fragile when the

0 commit comments

Comments
 (0)