Skip to content

Fix u64 underflow in binom() when k > n - #1075

Open
newmathphys wants to merge 1 commit into
TheAlgorithms:masterfrom
newmathphys:fix/binom-k-greater-than-n
Open

newmathphys wants to merge 1 commit into
TheAlgorithms:masterfrom
newmathphys:fix/binom-k-greater-than-n

Conversation

@newmathphys

Copy link
Copy Markdown

binom(n, k) underflows u64 when k > n: the loop runs for i in 0..k, so
it evaluates n - i for i > n.

$ cargo test            # debug build, overflow checks ON
thread 'main' panicked at ...:
attempt to subtract with overflow

$ cargo run --release   # checks OFF
binom(0, 2) -> 18446744073709551615   # garbage, exit code 0

All seven existing tests use k <= n, so the case is not covered:

test n, k
test_binom_5_2 5, 2
test_binom_10_5 10, 5
test_binom_0_0 0, 0
test_binom_large_n_small_k 1000, 2
test_binom_random_1 7, 4
test_binom_random_2 12, 3
test_binom_random_3 20, 10

Mathematically C(n, k) = 0 for k > n, so the function should return 0
rather than panic or return garbage.

The fix

Guard the case explicitly and keep the rest unchanged:

 pub fn binom(n: u64, k: u64) -> BigInt {
+    // C(n, k) = 0 for k > n. Without this guard the loop below evaluates
+    // `n - i` for i > n, which is invalid for u64: a debug build panics with
+    // "attempt to subtract with overflow", and a release build silently wraps
+    // to 18446744073709551615 and returns garbage instead of 0.
+    if k > n {
+        return BigInt::from(0);
+    }
+
     let mut res = BigInt::from_u64(1).unwrap();

A regression test is added in the same commit, so CI covers the case:

#[test]
fn test_binom_k_greater_than_n() {
    assert_eq!(binom(0, 2), BigInt::from(0));
    assert_eq!(binom(5, 7), BigInt::from(0));
}

Verification

The guard was checked against the seven existing expectations (no regression)
and against the three failing inputs, using the same arithmetic as the function
body:

binom(5,2)   = 10        expected 10        OK
binom(10,5)  = 252       expected 252       OK
binom(0,0)   = 1         expected 1         OK
binom(1000,2)= 499500    expected 499500    OK
binom(7,4)   = 35        expected 35        OK
binom(12,3)  = 220       expected 220       OK
binom(20,10) = 184756    expected 184756    OK

binom(0,2) = 0   (was: panic / garbage)
binom(5,7) = 0   (was: panic / garbage)
binom(1,3) = 0   (was: panic / garbage)

How this was found

Not by reading the code: by stating a contract for the expression n - i with
the loop's own condition (requires i < k, ensures result >= 0) and letting a
SMT solver refute it. The counterexample was n = 0, i = 1 — reachable as
binom(0, 2) — and was then reproduced with rustc (panic in debug, wrapped
garbage in release). See KERN, which
documents the case.

Happy to adjust the style (for example, documenting k <= n as a precondition
instead of returning 0) if you prefer that contract.

@newmathphys
newmathphys requested a review from imp2002 as a code owner October 22, 2026 16:17
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.89%. Comparing base (fed66cb) to head (98b1f6e).

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1075      +/-   ##
==========================================
- Coverage   95.89%   95.89%   -0.01%     
==========================================
  Files         396      396              
  Lines       30441    30448       +7     
==========================================
+ Hits        29192    29197       +5     
- Misses       1249     1251       +2     

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

newmathphys pushed a commit to newmathphys/kern that referenced this pull request Oct 22, 2026
Second real defect found in a third-party repository, and the second upstream
report from this project.

PR: TheAlgorithms/Rust#1075 (+16 -0, one file)
- guard `if k > n { return BigInt::from(0) }` in binom()
- regression test `test_binom_k_greater_than_n` so the repository's own CI
  covers the case from now on

The fix was verified before submitting: all seven existing expectations still
hold (binom(5,2)=10, binom(10,5)=252, binom(0,0)=1, binom(1000,2)=499500,
binom(7,4)=35, binom(12,3)=220, binom(20,10)=184756) and the three failing
inputs now return 0 instead of panicking or returning garbage.

Recorded in the journal entry #324 (RU + EN), the plan (step 7) and both
READMEs. Evidence: docs/evidence/thealgorithms_rust/ - the verbatim upstream
file, the shadow contract, the ground-truth program and log, the fixed file,
the PR body and the fix-verification log.

Gates: ci-fast 10/10, metrics-guard, llms-check, sync-check, docs-parity,
evidence-gate, secrets-check.
newmathphys pushed a commit to newmathphys/kern that referenced this pull request Oct 22, 2026
newmathphys pushed a commit to newmathphys/kern that referenced this pull request Oct 22, 2026
…s tracking

DEFECT #330. The .gitignore exception

    !docs/evidence/*.log

applies ONLY to the TOP LEVEL of the directory. Logs in SUBdirectories -
`div_guards/`, `leaderboard/`, `live_eval/`, `arith_semantics/`, `external/`,
`claims_audit/`, `megatest/`, `full_arsenal/`, `prime_sieve/`, `step4_while/`,
`step8_evm/`, `live_bughunt/` - were silently ignored, so 46 files that the
documentation LINKS TO were not in the repository. An external auditor who
followed such a link got a 404: the prose promised evidence that did not exist.

Measured before the fix:
    git check-ignore -v docs/evidence/external/verification.log
    -> .gitignore:54:*.log
    files on disk under docs/evidence/ but NOT in git: 46

That is the same class as #320 (a claim without a command that reproduces it),
which is why it matters more than a missing file usually would.

FIX
  * .gitignore: added `!docs/evidence/**/*.log` (plus .json, .txt, .jsonl)
    with a comment explaining WHY the single-star version was not enough;
  * all 46 files staged;
  * tools/evidence_gate.py now walks docs/evidence/ RECURSIVELY and fails when
    any file there is not tracked, printing the list and the exact .gitignore
    remedy.

NON-VACUITY: `git rm --cached` of one log turns the gate red (rc=2), then the
file is restored and the gate is green again.

A HARNESS DEFECT FOUND BY RUNNING THE CASE. The first version of the can-fail
case did `git rm --cached` and left it there. The harness rolls back FILES from
its snapshot but does not touch the INDEX, so after one run the log stayed
untracked and `evidence-gate` was red permanently - the case broke the next
run. The case is now self-healing: it removes the file from the index, runs the
gate, restores the index in the same shell command, and returns the GATE's exit
code, not `git add`'s. Verified: PASS, non-vacuous, index clean afterwards.

ALSO IN THIS COMMIT (step B, remaining items)
  docs/evidence/external/verification.log - what OUTSIDE parties confirmed:
    Codecov on TheAlgorithms/Rust#1075: ci_passed true, base 30441 lines /
      95.89% -> head 30448 / 95.89%, "all modified and coverable lines are
      covered by tests";
    TheAlgorithms/C#1595   open, mergeable, +7/-0,  0 comments;
    TheAlgorithms/Rust#1075 open, mergeable, +16/-0, 1 comment (the Codecov
      bot; no maintainer response yet);
    release v2.6.4 draft=false with 4 assets INCLUDING kern.intoto.jsonl and
      kern.intoto.sig, so the binary can be verified against the public key.

Registry: 191 targets, CHECK 152/152, 174 can-fail cases.
Gates: ci-fast, claims-audit, evidence-gate, metrics-guard, llms-check,
sync-check, docs-parity, version-check, secrets-check, contracts, audits,
codecheck - all green.
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.

2 participants