`remainderBy 0` puts `NaN` in an `Int`, while its sibling `modBy 0` kills the program
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 48
- Forks
- 14
- Avg merge
- 4h 14m
- Merged PRs (30d)
- 1
Description
Repository: gren-lang/core
Found against: gren 0.6.6, gren-lang/core 7.4.2, node 22
Summary
Three functions in Math divide, and a zero divisor gets three different
answers:
| expression | result | kind of answer |
|---|---|---|
7 // 0 |
0 |
a number |
Math.remainderBy 0 7 |
NaN |
not an Int |
Math.modBy 0 7 |
program stops | a runtime exception, sometimes silent |
remainderBy's type is Int -> Int -> Int, and it returns a value that is not an integer.
The NaN then propagates: it compares unequal to
everything including itself, it survives String.fromInt as the text "NaN",
and it corrupts any Int accumulator it reaches.
Reproduction
One program asks both functions, in order:
module Main exposing (main)
import Init
import Math
import Node
import Stream
import Task exposing (Task)
zero : Int
zero =
String.count "ab" - String.count "ab"
main : Node.SimpleProgram a
main =
Node.defineSimpleProgram
(\env -> Node.endSimpleProgram (emit env))
emit : Node.Environment -> Task Never {}
emit env =
Stream.writeLineAsBytes
("remainderBy 0 7 = " ++ String.fromInt (Math.remainderBy zero 7))
env.stdout
|> Task.andThen
(\out ->
Stream.writeLineAsBytes
("modBy 0 7 = " ++ String.fromInt (Math.modBy zero 7))
out
)
|> Task.map (\_ -> {})
|> Task.onError (\_ -> Task.succeed {})
$ gren make Main --output=app
$ node app
remainderBy 0 7 = NaN
$ echo $?
0
The first line is the bug this report is about: an Int-typed expression
printed as NaN. Bind it — r = Math.remainderBy zero 7 — and it keeps
answering as a float: r == 0 is False, r == r is False, and toFloat r
is NaN.
The second line never prints. stderr is empty — zero bytes — and the exit
status is 0, so the program that died looks exactly like a program that
finished.
The modBy half is quieter than it looks
Put the modBy 0 in the program's first effect instead — the same program,
with emit shortened to a single write:
emit : Node.Environment -> Task Never {}
emit env =
Stream.writeLineAsBytes
("modBy 0 7 = " ++ String.fromInt (Math.modBy zero 7))
env.stdout
|> Task.map (\_ -> {})
|> Task.onError (\_ -> Task.succeed {})
and it crashes, with an error message, but exits with 0:
$ node app
Error: Cannot perform mod 0. Division by zero error.
at _Debug_crash (app:1049:13)
…
at _Scheduler_step (app:295:23)
at _Platform_dispatchEffects (app:1384:5)
$ echo $?
0
The difference is where the crash lands, and it has nothing to do with modBy.
In the program's first synchronous effect the exception is still inside the
try/catch that Generate/Node.hs wraps around the program, which prints it
without setting a status — that is gren-lang/compiler#385. One write later that
catch is gone, and core's own stream kernel takes the exception instead,
relabels it a stream cancellation and stops the scheduler; that is a separate
defect, filed as https://github.com/gren-lang/core/issues/142
Either way the status is 0. However, depending on where modBy 0 is
reached from, the crash may be quiet, or obvious.
Cause
core/src/Gren/Kernel/Math.js:
var _Math_remainderBy = F2(function (b, a) {
return a % b;
});
var _Math_modBy = F2(function (modulus, x) {
var answer = x % modulus;
return modulus === 0
? __Debug_crash(11)
: …
});
modBy guards the zero divisor (and crashes)
remainderBy does not.
// is unguarded too but survives by accident: _Basics_idiv is Math.trunc(a / b), which is
Infinity, and the code generator's inline form appends | 0, which turns
that into 0.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
The behavior is implemented in core/src/Gren/Kernel/Math.js; start by comparing _Math_remainderBy with _Math_modBy and locate the existing Math test entry point. Reproduce the supplied Gren program, establish the intended zero-divisor behavior for remainderBy, and add regression coverage showing that an Int result does not become NaN.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100