fix(core): return error instead of panicking on integer division by zero 💥#168
Merged
Conversation
timfennis
commented
Jun 2, 2026
…ero 💥
Integer `%`, `%%` and `\` by zero crashed the interpreter with a Rust
panic ("attempt to divide by zero") originating in i64/num-bigint
arithmetic. `1 % 0`, `5 \ 0`, a BigInt `% 0` and a rational `% 0` all
aborted the process instead of producing a recoverable runtime error.
Guard the exact-arithmetic remainder and floor-division paths against a
zero divisor and surface a "division by zero" error. Float `%` keeps its
IEEE NaN behaviour and `/` keeps returning infinity, matching the
existing documented semantics.
Adds functional regression tests and documents the behaviour in the
manual.
The previous commit over-corrected: it made floor division (`\`) by zero raise an error for every operand type. But only the i64 fast path actually panicked — rational and BigInt floor division by zero already promoted to a float and returned infinity, matching `/`. Erroring there was a regression. Guard just the i64 `div_euclid` fast path (which also panics on `i64::MIN \ -1`) and fall back to the general float-promoting path, so all floor divisions by zero return `inf` again. Remainder (`%`, `%%`) still errors, since those genuinely panicked before and have no infinite result. Updates the floor-div regression tests and the manual accordingly.
bed32e7 to
272c7fc
Compare
timfennis
added a commit
that referenced
this pull request
Jun 2, 2026
…ases 💥 (#171) ## Context While fuzzing the pipeline for panics, I found that the integer-power (`^`) path aborts the whole process on several negative-exponent inputs. These are recoverable runtime errors that should never crash the interpreter — same class of bug as the recent integer division-by-zero fix (#168). ## The crashes | Input | Panic | |---|---| | `2 ^ (1/-1)` | `right hand side must not be negative` (`int.rs:117`) | | `0 ^ -1` | `denominator == 0` (num-rational) | | `0 ^ (2/-1)` | `right hand side must not be negative` | **Root cause** — `Number::pow` had two integer-exponent branches: - `Int ^ Int` handled negative exponents as the reciprocal `1/(base^|exp|)`, but with base `0` that builds `BigRational::new(1, 0)`, which panics. - `Int ^ Rational` (integer-valued rational, e.g. `1/-1`) didn't handle negatives at all — it called `Int::pow` directly, which panics unconditionally on a negative RHS. The existing `MAX_EXPONENT_BITS` guard only checks magnitude, so it never caught these. ## Changes - Added a `Number::int_pow(base, exponent)` helper that both integer-exponent branches route through. It returns a `division by zero` error for `0 ^ negative`, computes the reciprocal rational for any other negative exponent, and otherwise returns the plain integer power. This also removes the previously duplicated negative-exponent logic. - Regression tests: - `900_bugs/bug0025_pow_negative_rational_exponent.ndc` — `2 ^ (1/-1)` → `1/2` - `001_math/030_pow_zero_negative_exponent.ndc` — `0 ^ -1` → `division by zero` - `001_math/031_pow_zero_negative_rational_exponent.ndc` — `0 ^ (2/-1)` → `division by zero` Valid cases verified unchanged: `2 ^ -1` → `1/2`, `3 ^ (-4/2)` → `1/9`, `2 ^ 3` → `8`, `(1/2) ^ -3` → `8`. 🤖 Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Integer
%,%%and\by zero crashed the interpreter with a Rust panic (attempt to divide by zero) originating ini64/num-bigintarithmetic, aborting the whole process instead of producing a recoverable runtime error.The simplest reproducer:
The same panic was reachable through several operators / operand types:
5 % 0(int modulo)error[vm]: division by zero5 \ 0(floor div)error[vm]: division by zerobigint % 0error[vm]: division by zero(1/3) % 0(rational)error[vm]: division by zero0 % 0error[vm]: division by zero5 / 0infinf(unchanged)5.0 % 0.0NaNNaN(unchanged)Changes
ndc_core/src/num.rs— hand-writtenNumber: Remimpl that rejects exact (int/rational) remainder by zero up front; same guard added tofloor_divandchecked_rem_euclid. Added aNumber::is_zerohelper.ndc_stdlib/src/math.rs— replaced the macro-generated integer%operator (whose fast-path fallback panicked) with one that checks for a zero divisor explicitly while preserving thei64::MIN % -1overflow fallback..ndcfunctional tests undertests/functional/programs/001_math/(auto-discovered bybuild.rs).number.md./keeps returning infinity and float%keeps returningNaN, matching the existing documented semantics — only the exact-arithmetic integer/rational paths that previously panicked now error cleanly.Verification
cargo fmtcleancargo clippy— no new warnings (28 pre-existing in both baseline and current)cargo test— all pass, including the 5 new regression testshttps://claude.ai/code/session_01RaWdHLsFAeQCf423gGpt3h
Generated by Claude Code