Skip to content

Fix SIGFPE on n=0 and silent wrong output from n=11 in math/catalan.c - #1595

Open
newmathphys wants to merge 1 commit into
TheAlgorithms:masterfrom
newmathphys:fix/catalan-zero-and-overflow
Open

newmathphys wants to merge 1 commit into
TheAlgorithms:masterfrom
newmathphys:fix/catalan-zero-and-overflow

Conversation

@newmathphys

Copy link
Copy Markdown

math/catalan.c has two defects. The first was reported before (#686, opened 2020-10-19, closed 2021-02-17) but the file still contains it.

1. Crash on n = 0

factorial initialises its accumulator as fac = x (line 9). For x = 0 the loop body never runs, so factorial(0) returns 0 — while 0! is 1. Line 25 then computes f1 / (f2 * f3) with integer division, so with n = 0 the divisor factorial(1) * factorial(0) is 1 * 0 = 0:

$ gcc -O0 -o catalan math/catalan.c     # compiles clean, exit 0
$ echo 0 | ./catalan
Floating point exception (core dumped)  # SIGFPE, exit code 136
$ echo 5 | ./catalan
42.00                                   # correct, exit 0

2. Silent wrong output from n = 11

f1 = factorial(2 * n) must fit in long int. 20! = 2432902008176640000 fits; 22! = 1124000727777607680000 does not. Beyond that the product overflows and the program prints garbage with exit code 0:

n correct Catalan printed exit code
10 16796 16796.00 0
11 58786 -65.00 0
12 208012 -2.00 0
13 742900 0.00 0
20 6564120420 0.00 0

A silent wrong answer is worse than a crash: nothing signals that the number is meaningless.

Note for reviewers — the obvious fix is wrong

Changing fac = x to fac = 1 does not work. The loop multiplies by (x-1), (x-2), …, 1, so it is fac = x that yields x!. With fac = 1 the function returns (x-1)! and n = 5 prints 126.00 instead of 42.00.

The fix

Keep fac = x, handle x == 0 explicitly, and guard the range that the factorial formula cannot serve:

     long int fac;  // fac stores x factorial
+    if (x == 0)
+        return 1;  // 0! = 1
     fac = x;
...
     scanf("%d", &n);
+    if (n < 0 || n > 10)
+    {
+        printf("n must be in [0, 10]: (2n)! overflows long int beyond that\n");
+        return 1;
+    }

Verification after the patch

n=0   -> 1.00
n=1   -> 1.00
n=2   -> 2.00
n=5   -> 42.00
n=10  -> 16796.00
n=11  -> "n must be in [0, 10]: (2n)! overflows long int beyond that"   exit 1
n=-1  -> "n must be in [0, 10]: (2n)! overflows long int beyond that"   exit 1

The change is intentionally minimal (bug fix only). Happy to convert main() to assert()-based tests as suggested in CONTRIBUTING.md if you prefer that in the same PR — just say so.

How the first defect was found

Not by reading the code, and not by the program's own tests: by stating a contract and letting a solver refute it. The divisor obligation factorial(n+1) * factorial(n) > 0 was refuted with the counterexample n = 0, result = 0, and the counterexample was then reproduced by gcc on the unmodified file — see KERN, which documents the case in docs/SHADOW_AUDIT_EXAMPLE.md.

The second defect (the overflow) came from arithmetic reasoning about 22! versus 2^63 - 1, not from the solver.

newmathphys pushed a commit to newmathphys/kern that referenced this pull request Oct 21, 2026
Upstream contribution:
- The minimal fix for the two defects KERN found in TheAlgorithms/C
  math/catalan.c was submitted as TheAlgorithms/C#1595 (22,554 stars,
  one file, +7 -0, CI `triage` passed). Fork newmathphys/C, branch
  fix/catalan-zero-and-overflow, commit 94553ec from base master@e5dad3fa.
- PR text and the patch as submitted live in docs/pr/; the patch was
  verified with `patch -p1` on a clean checkout.
- The link is recorded in docs/SHADOW_AUDIT_EXAMPLE.md, README.md,
  README.ru.md and journal entry #319 (RU + EN).

Also in this release (already on main after the v2.6.2 tag):
- #318 fixed: false [CRASH PATH] on a guarded division; path conditions
  are now resolved per position instead of accumulated.

Version bumped to v2.6.3 across src/main.zig, src/next/main.zig,
src/capi.zig, server.json, action.yml, smithery.yaml, MCP manifests,
README.md, docs/index.html and docs/facts.json; CHANGELOG section added.

Gates: ci-fast 10/10, version-check, metrics-guard, llms-check,
sync-check, docs-parity, changelog-check, xray-check,
arith-semantics-check, codecheck - all green.
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.

This branch has not been deployed

No deployments
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.

1 participant