Skip to content

Fix Rational.zero producing 0/0 that SIGFPEs on any arithmetic - #8

Merged
hellerve merged 1 commit into
masterfrom
claude/fix-zero-sigfpe
Jul 11, 2026
Merged

Fix Rational.zero producing 0/0 that SIGFPEs on any arithmetic#8
hellerve merged 1 commit into
masterfrom
claude/fix-zero-sigfpe

Conversation

@carpentry-agent

Copy link
Copy Markdown

What

(Rational.zero) returned a struct with both numerator and denominator set to 0 (i.e. 0/0), because the module used (derive Rational zero). Every arithmetic operation divides by the denominator, so the most basic use — x + 0 — crashed the program with a SIGFPE (confirmed: exit 136):

(Rational.add (Rational.new 3 4) (Rational.zero)) ; => SIGFPE, exit 136

Fix

Replace the broken derive with an explicit constructor inside the module, mirroring how Semver defines its own zero:

(defn zero [] (init 0 1))
(implements zero Rational.zero)

zero is now 0/1, so it is a proper additive identity and safe to divide by. The same call now returns (Rational 3/4) and exits cleanly.

Tests

Added assertions covering the constructor's value (0/1) and the identity laws that previously crashed — x + 0, x - 0, and x * 0. The full suite passes (127 tests), and carp-fmt -c / angler are clean on both changed files.


Opened by the carpentry-org heartbeat agent (Claude). Veit has not reviewed this yet.

`(derive Rational zero)` generated a zero struct with both numerator and
denominator set to 0. Any operation dividing by the denominator (e.g.
`(add x (zero))`) then crashed with SIGFPE.

Replace the derive with an explicit `(defn zero [] (init 0 1))`, so `zero`
is 0/1 and safe in arithmetic, and add tests covering the value and the
additive-identity laws.

@carpentry-reviewer carpentry-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Build & Tests

Checked out claude/fix-zero-sigfpe and ran the suite locally (carp -x tests/rational.carp): 127 passed, 0 failed. CI is green on ubuntu + macos. Builds clean.

Findings

Verified the root cause and the fix in full:

  • Mechanism confirmed. On master, (derive Rational zero) expands to (init 0 0) — a 0/0 struct. add/sub/mul compute the result denominator as da*db, so any op against (zero) hits d = 0, and new then evaluates (/ n (gcd n 0)) = (/ n 0) → SIGFPE. The reproduction in the description is accurate.
  • Fix is minimal and idiomatic. (defn zero [] (init 0 1)) mirrors the existing from-int ((init i 1)) exactly — init is the raw private constructor and needs no reduction for a unit denominator. 0/1 is a true additive identity and zero? (numerator-only) still reports it as zero.
  • No stale dependencies. Nothing else in the module relied on the derived zero; the (implements zero Rational.zero) keeps generic (zero) dispatch working.

I also ran an edge-case script beyond the test suite — every operation that divides by the denominator, applied to (zero):

add 3/4 + zero  => (Rational 3/4)     ; the exact expr that SIGFPEd on master
zero + zero     => (Rational 0/1)
zero / (3/4)    => (Rational 0/1)
sign/floor/ceil/round zero => (Rational 0/1)
to-double zero  => 0    to-int zero => 0    zero? zero => true

All clean, no crashes. No CHANGELOG exists in this repo, so nothing to update there.

Verdict: merge

Correct, minimal, well-tested fix for a genuine whole-program crash on the most basic use of a core constructor. Builds, passes tests and CI, and holds up under edge-case probing.

@hellerve
hellerve merged commit 4ac3aef into master Jul 11, 2026
2 checks passed
@hellerve
hellerve deleted the claude/fix-zero-sigfpe branch July 11, 2026 19:00
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