From ebfd89a0ef7b3616e19c5cba11a0469109488594 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Sat, 4 Jul 2026 17:13:06 -0700 Subject: [PATCH 1/3] Add failing tests for a bug in B-Z division --- tests/biguint.rs | 24 ++++++++++++++++++++++++ tests/fuzzed.rs | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/tests/biguint.rs b/tests/biguint.rs index cda45cca..a85c9429 100644 --- a/tests/biguint.rs +++ b/tests/biguint.rs @@ -946,6 +946,30 @@ fn test_div_rem_burnikel_ziegler_u1_equals_d() { assert_eq!(r, remainder, "remainder should be 42"); } +#[test] +fn test_div_rem_burnikel_ziegler_a1_equals_b1() { + // This triggered a bug for the missing a1 >= b1 guard in div_three_halves_by_two + // (Algorithm 2, step 3b of the Burnikel-Ziegler paper). + // + // d has 65 u64-digits (130 u32-digits). In div_two_digit_by_one, the normalization + // shift pads d to fill `level` digits. After div_two_digit_by_one_normalized splits + // both ah and the shifted d at the half-level, the second div_three_halves_by_two call + // receives a remainder whose upper half equals b1 (the upper half of the shifted d). + // It then called div_two_digit_by_one(a1, a2, b1, level) with a1 == b1, violating the + // a1 < b1 precondition. + // + // The quotient ((1 << 4096) - 1) is chosen so the remainder is d - 1, which shares + // the same upper half as d after normalization. + let d = (BigUint::one() << 4159usize) + BigUint::one(); + let expected_q = (BigUint::one() << 4096usize) - BigUint::one(); + let expected_r = &d - BigUint::one(); + let u = &d * &expected_q + &expected_r; + + let (q, r) = u.div_rem(&d); + assert_eq!(q, expected_q, "quotient mismatch"); + assert_eq!(r, expected_r, "remainder mismatch"); +} + #[test] fn test_div_ceil() { fn check(a: &BigUint, b: &BigUint, d: &BigUint, m: &BigUint) { diff --git a/tests/fuzzed.rs b/tests/fuzzed.rs index 7ff56419..3adbae1b 100644 --- a/tests/fuzzed.rs +++ b/tests/fuzzed.rs @@ -183,3 +183,44 @@ fn fuzzed_mul_2() { assert_eq!(c * a * b, result); assert_eq!(c * b * a, result); } + +#[test] +fn fuzzed_sqrt_1() { + // This input failed an assertion in the Burnikel-Ziegler division algorithm, but only on + // 32-bit. See also `test_div_rem_burnikel_ziegler_a1_equals_b1` for a more targeted test + // which failed on both 32-bit and 64-bit targets. + let hex = "\ + ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\ + ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\ + ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\ + ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\ + ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\ + ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\ + ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\ + ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\ + ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\ + ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\ + ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\ + ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\ + ffffffffffffffffffffffffffffffff974810cf4c437ba04120a1154166bbef02433931c39106e4\ + 6f2837ef218ee18e59ae8d861c29c5b276b221dfc125c5cff35dcadc6caef8f005fc56a94c280c66\ + 8118da37878e14248f36ae7350949811f342eba025d9ae5164b650727dd1c772504faadc3620bdb7\ + cfee873de995aae72d18e100a8577eac452fc94350cfdafbe88f23c1aba8d830e517c0b4a76ff158\ + ece8d7f2ebba856777075fe6a9bd6b1026933a35f287614f2903433e6159b4bba785b6b9b43c78d6\ + 1a93549d0be51d466602ea3d63251ee5b99efcc8e2acf47f9ef0cee82caf2d001295a3d27fec6eba\ + 3995492f907ab2241b1127740ab045796990bf62b6c8b1099dcf291df3184c5b9b4262a4752ece67\ + d8e470674b27731012d9599c129bfa8dcd77990c747915f919cbb81319ede35b942d1cad3d90e33f\ + 9d4d4bb9a856d74e2bc7dd08a199c12c0f7f7b1832725ec180d23347ea657c027af747e2d103e585\ + 1f8c9fdfe738138d30fa810c59b53d1f8d7fb409e97a46c58049de0ebe7d5ae92fe1fb2e8d762d03\ + 3027158643541a06891dde1258624d714389f68b532406c10692d83b97262078ff66cb4907bd7273\ + 6a614764f22801d93cef8ba49fc2482cea302ac9dc0822fe341350dce3feaecd80651415971d4a1d\ + cf8c0b3cdb189928321496a0ace3ab8d566b9afb26a812fa1fea858929006cdbd988d7b3ee0fb951\ + 87c8e4e120f63a4fab417604b9b4e86a2158cbc52c4a6f76c6bfee18b4969cba2c5e8408ecc80cb3\ + b371469d375c372c0000000000000000000000000000000000000000000000000000000000000000\ + 00000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000"; + + let x = BigUint::from_str_radix(hex, 16).unwrap(); + let r = x.sqrt(); + assert!(r.pow(2) <= x && (r + 1u32).pow(2) > x); +} From f4a43f56eb38f6b2ea52b21b47e5392920fe158b Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Sat, 4 Jul 2026 17:43:38 -0700 Subject: [PATCH 2/3] Fix the missing part of the Burnikel-Ziegler algorithm --- src/biguint/division.rs | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/biguint/division.rs b/src/biguint/division.rs index b4146963..bbd10de8 100644 --- a/src/biguint/division.rs +++ b/src/biguint/division.rs @@ -295,19 +295,34 @@ fn div_rem_burnikel_ziegler(u: &BigUint, d: &BigUint) -> (BigUint, BigUint) { b2: BigUint, level: usize, ) -> (BigUint, BigUint) { - let (mut q, c) = div_two_digit_by_one(a1, a2, b1.clone(), level); - let (mut r, rsub) = (concat_biguint(&c, a3, level), &q * &b2); + // Algorithm 2 step 3 distinguishes A₁ Date: Sat, 4 Jul 2026 17:56:57 -0700 Subject: [PATCH 3/3] Release 0.5.1 --- Cargo.toml | 2 +- RELEASES.md | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 0019dbf7..7935e31a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ categories = [ "algorithms", "data-structures", "science" ] license = "MIT OR Apache-2.0" name = "num-bigint" repository = "https://github.com/rust-num/num-bigint" -version = "0.5.0" +version = "0.5.1" readme = "README.md" exclude = ["/ci/*", "/.github/*"] edition = "2021" diff --git a/RELEASES.md b/RELEASES.md index 5f7bbeec..3f08b6d0 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,11 @@ +# Release 0.5.1 (2026-07-04) + +- [Fixed a regression in the B-Z division algorithm.][348] + +[348]: https://github.com/rust-num/num-bigint/pull/338 + +**Contributors**: @cuviper + # Release 0.5.0 (2026-07-02) - [Upgrade to `rand` v0.10 and/or v0.9, and split `rand_core`][338].