From 3de765066ea0fbfa770bb5d609f722588e981c43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Furga=C5=82a?= <83299832+00200200@users.noreply.github.com> Date: Tue, 29 Sep 2026 17:42:06 +0200 Subject: [PATCH 1/2] Fix integer overflow in combinations size-hint tests. The naive product-then-divide binomial helper overflowed usize (C(27, 7) on 32-bit, C(21, 19) even on 64-bit). Use the same overflow-safe algorithm as checked_binomial. --- tests/quick.rs | 22 ++++++++++++++++------ tests/test_std.rs | 34 +++++++++++++++++++++++++++++----- 2 files changed, 45 insertions(+), 11 deletions(-) diff --git a/tests/quick.rs b/tests/quick.rs index b4129a305..3e46a0e14 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -961,12 +961,22 @@ quickcheck! { } } -fn binomial(n: usize, k: usize) -> usize { - if k > n { - 0 - } else { - (n - k + 1..=n).product::() / (1..=k).product::() - } +fn binomial(mut n: usize, mut k: usize) -> usize { + if n < k { + return 0; + } + // Same algorithm as `checked_binomial` in `src/adaptors/mod.rs`. + k = (n - k).min(k); + let mut c = 1usize; + for i in 1..=k { + c = (c / i) + .checked_mul(n) + .unwrap() + .checked_add((c % i).checked_mul(n).unwrap() / i) + .unwrap(); + n -= 1; + } + c } quickcheck! { diff --git a/tests/test_std.rs b/tests/test_std.rs index 6800166f9..bc6d489cb 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -1137,12 +1137,36 @@ fn combinations_zero() { it::assert_equal((0..0).combinations(0), vec![vec![]]); } -fn binomial(n: usize, k: usize) -> usize { - if k > n { - 0 - } else { - (n - k + 1..=n).product::() / (1..=k).product::() +fn binomial(mut n: usize, mut k: usize) -> usize { + if n < k { + return 0; } + // Same algorithm as `checked_binomial` in `src/adaptors/mod.rs`. The naive + // "product then divide" formula overflows `usize` even when the result + // fits, which panics `combinations_inexact_size_hints` on 32-bit (#995). + k = (n - k).min(k); + let mut c = 1usize; + for i in 1..=k { + c = (c / i) + .checked_mul(n) + .unwrap() + .checked_add((c % i).checked_mul(n).unwrap() / i) + .unwrap(); + n -= 1; + } + c +} + +#[test] +fn binomial_avoids_intermediate_overflow() { + // The naive "product then divide" formula overflows `usize` even when the + // binomial coefficient itself fits. C(21, 19) == C(21, 2) == 210, but + // (3..=21).product::() overflows on 64-bit. The same class of bug + // panics `combinations_inexact_size_hints` on 32-bit (#995): that test + // evaluates C(27, 7) because `Filter`'s size-hint upper bound is 18 and + // the lazy buffer has already loaded 9 items. + assert_eq!(binomial(21, 19), 210); + assert_eq!(binomial(27, 7), 888030); } #[test] From e6fce475a9bdb68ac40a9181c413bec34e2bb02d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Furga=C5=82a?= <83299832+00200200@users.noreply.github.com> Date: Sat, 17 Oct 2026 11:34:31 +0200 Subject: [PATCH 2/2] Reuse checked_binomial in integration tests --- src/adaptors/mod.rs | 3 ++- src/lib.rs | 3 +++ tests/quick.rs | 18 ++---------------- tests/test_std.rs | 20 ++------------------ 4 files changed, 9 insertions(+), 35 deletions(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index b5c0d42b1..7bd05a7d4 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -834,8 +834,9 @@ impl_tuple_combination!(Tuple10Combination Tuple9Combination; a b c d e f g h i) impl_tuple_combination!(Tuple11Combination Tuple10Combination; a b c d e f g h i j); impl_tuple_combination!(Tuple12Combination Tuple11Combination; a b c d e f g h i j k); +/// Computes the binomial coefficient, returning `None` if it overflows `usize`. // https://en.wikipedia.org/wiki/Binomial_coefficient#In_programming_languages -pub(crate) fn checked_binomial(mut n: usize, mut k: usize) -> Option { +pub fn checked_binomial(mut n: usize, mut k: usize) -> Option { if n < k { return Some(0); } diff --git a/src/lib.rs b/src/lib.rs index 51726e3b9..da602385a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -158,6 +158,9 @@ pub mod traits { pub use crate::tuple_impl::HomogeneousTuple; } +// Shared with integration tests. +#[doc(hidden)] +pub use crate::adaptors::checked_binomial; #[cfg(feature = "use_alloc")] use crate::combinations_with_replacement::ArrayCombinationsWithReplacement; pub use crate::concat_impl::concat; diff --git a/tests/quick.rs b/tests/quick.rs index 3e46a0e14..c82247b4e 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -961,22 +961,8 @@ quickcheck! { } } -fn binomial(mut n: usize, mut k: usize) -> usize { - if n < k { - return 0; - } - // Same algorithm as `checked_binomial` in `src/adaptors/mod.rs`. - k = (n - k).min(k); - let mut c = 1usize; - for i in 1..=k { - c = (c / i) - .checked_mul(n) - .unwrap() - .checked_add((c % i).checked_mul(n).unwrap() / i) - .unwrap(); - n -= 1; - } - c +fn binomial(n: usize, k: usize) -> usize { + itertools::checked_binomial(n, k).unwrap() } quickcheck! { diff --git a/tests/test_std.rs b/tests/test_std.rs index bc6d489cb..9a126756e 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -1137,24 +1137,8 @@ fn combinations_zero() { it::assert_equal((0..0).combinations(0), vec![vec![]]); } -fn binomial(mut n: usize, mut k: usize) -> usize { - if n < k { - return 0; - } - // Same algorithm as `checked_binomial` in `src/adaptors/mod.rs`. The naive - // "product then divide" formula overflows `usize` even when the result - // fits, which panics `combinations_inexact_size_hints` on 32-bit (#995). - k = (n - k).min(k); - let mut c = 1usize; - for i in 1..=k { - c = (c / i) - .checked_mul(n) - .unwrap() - .checked_add((c % i).checked_mul(n).unwrap() / i) - .unwrap(); - n -= 1; - } - c +fn binomial(n: usize, k: usize) -> usize { + it::checked_binomial(n, k).unwrap() } #[test]