Skip to content

Commit f90d8af

Browse files
authored
feat(runtime): Allow modulo on floating point numbers (#1914)
1 parent 86c6e12 commit f90d8af

File tree

5 files changed

+152
-13
lines changed

5 files changed

+152
-13
lines changed

compiler/test/TestFramework.re

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ let test_data_dir = Fp.At.(test_dir / "test-data");
2727
let test_input_dir = Fp.At.(test_dir / "input");
2828
let test_output_dir = Fp.At.(test_dir / "output");
2929
let test_stdlib_dir = Fp.At.(test_dir / "stdlib");
30+
let test_runtime_dir = Fp.At.(test_dir / "runtime");
3031
let test_snapshots_dir = Fp.At.(test_dir / "__snapshots__");
3132

3233
let test_grainfmt_dir = Fp.At.(test_dir / "grainfmt");

compiler/test/runner.re

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ let grainfile = name =>
2020
Filepath.to_string(Fp.At.(test_input_dir / (name ++ ".gr")));
2121
let stdlibfile = name =>
2222
Filepath.to_string(Fp.At.(test_stdlib_dir / (name ++ ".gr")));
23+
let runtimefile = name =>
24+
Filepath.to_string(Fp.At.(test_runtime_dir / (name ++ ".gr")));
2325
let wasmfile = name =>
2426
Filepath.to_string(Fp.At.(test_output_dir / (name ++ ".gr.wasm")));
2527
let watfile = name =>
@@ -392,6 +394,21 @@ let makeStdlibRunner = (test, ~code=0, name) => {
392394
});
393395
};
394396

397+
let makeRuntimeRunner = (test, ~code=0, name) => {
398+
test(name, ({expect}) => {
399+
Config.preserve_all_configs(() => {
400+
// Run stdlib suites in release mode
401+
Config.profile := Some(Release);
402+
let infile = runtimefile(name);
403+
let outfile = wasmfile(name);
404+
ignore @@ compile_file(infile, outfile);
405+
let (result, exit_code) = run(outfile);
406+
expect.int(exit_code).toBe(code);
407+
expect.string(result).toEqual("");
408+
})
409+
});
410+
};
411+
395412
let parse = (name, lexbuf, source) => {
396413
let ret = Grain_parsing.Driver.parse(~name, lexbuf, source);
397414
open Grain_parsing;
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
module NumberTest
2+
3+
include "runtime/numbers"
4+
5+
// Simple isNaN
6+
let isNaN = x => x != x
7+
let isInfinite = x => x == Infinity || x == -Infinity
8+
from Numbers use { (%) }
9+
10+
// (%)
11+
assert 20 % 4 == 0
12+
assert 20 % 3 == 2
13+
assert 15 % 6 == 3
14+
assert -10 % 3 == 2
15+
assert 5 % 2.75 == 2.25
16+
assert 3.0 % 2.0 == 1.0
17+
assert 3.0 % -2.0 == 1.0
18+
assert -3.0 % 2.0 == -1.0
19+
assert -3.0 % -2.0 == -1.0
20+
assert 3.5 % 2.0 == 1.5
21+
assert 3.5 % -2.0 == 1.5
22+
assert -3.5 % 2.0 == -1.5
23+
assert -3.5 % -2.0 == -1.5
24+
assert 3.0 % 2.5 == 0.5
25+
assert 3.0 % -2.5 == 0.5
26+
assert -3.0 % 2.5 == -0.5
27+
assert -3.0 % -2.5 == -0.5
28+
assert 0.5 % 1.0 == 0.5
29+
assert 0.5 % -1.0 == 0.5
30+
assert -0.5 % 1.0 == -0.5
31+
assert -0.5 % -1.0 == -0.5
32+
assert 1.5 % 1.0 == 0.5
33+
assert 1.5 % -1.0 == 0.5
34+
assert -1.5 % 1.0 == -0.5
35+
assert -1.5 % -1.0 == -0.5
36+
assert 1.25 % 1.0 == 0.25
37+
assert 1.25 % -1.0 == 0.25
38+
assert -1.25 % 1.0 == -0.25
39+
assert -1.25 % -1.0 == -0.25
40+
assert 1.0 % 1.25 == 1.0
41+
assert 1.0 % -1.25 == 1.0
42+
assert -1.0 % 1.25 == -1.0
43+
assert -1.0 % -1.25 == -1.0
44+
assert -13 % 64 == 51
45+
assert isNaN(0.0 % 0.0)
46+
assert isNaN(-0.0 % 0.0)
47+
assert isNaN(0.0 % -0.0)
48+
assert isNaN(-0.0 % -0.0)
49+
assert 0.0 % 1.0 == 0.0
50+
assert -0.0 % 1.0 == -0.0
51+
assert 0.0 % -1.0 == 0.0
52+
assert -0.0 % -1.0 == -0.0
53+
assert isNaN(1.0 % 0.0)
54+
assert isNaN(-1.0 % 0.0)
55+
assert isNaN(1.0 % -0.0)
56+
assert isNaN(-1.0 % -0.0)
57+
assert isNaN(NaN % 0.0)
58+
assert isNaN(NaN % -0.0)
59+
assert isNaN(NaN % 1.0)
60+
assert isNaN(NaN % -1.0)
61+
assert isNaN(NaN % 0.0)
62+
assert isNaN(NaN % -0.0)
63+
assert isNaN(NaN % 1.0)
64+
assert isNaN(NaN % -1.0)
65+
assert isNaN(NaN % NaN)
66+
assert 0.0 % Infinity == 0.0
67+
assert -0.0 % Infinity == -0.0
68+
assert 0.0 % -Infinity == 0.0
69+
assert -0.0 % -Infinity == -0.0
70+
assert 1.0 % Infinity == 1.0
71+
assert -1.0 % Infinity == -1.0
72+
assert 1.0 % -Infinity == 1.0
73+
assert -1.0 % -Infinity == -1.0
74+
assert isNaN(Infinity % 0.0)
75+
assert isNaN(Infinity % -0.0)
76+
assert isNaN(-Infinity % 0.0)
77+
assert isNaN(-Infinity % -0.0)
78+
assert isNaN(Infinity % 1.0)
79+
assert isNaN(Infinity % -1.0)
80+
assert isNaN(-Infinity % 1.0)
81+
assert isNaN(-Infinity % -1.0)
82+
assert isNaN(Infinity % Infinity)
83+
assert isNaN(-Infinity % Infinity)
84+
assert isNaN(Infinity % -Infinity)
85+
assert isNaN(-Infinity % -Infinity)
86+
assert isNaN(Infinity % NaN)
87+
assert isNaN(-Infinity % NaN)
88+
assert isNaN(NaN % Infinity)
89+
assert isNaN(NaN % -Infinity)
90+
assert -17 % 4 == 3
91+
assert -17 % -4 == -1
92+
assert 17 % -4 == 5
93+
assert -17 % 17 == 0
94+
assert 17 % -17 == 0
95+
assert 17 % 17 == 0

compiler/test/suites/runtime.re

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
open Grain_tests.TestFramework;
2+
open Grain_tests.Runner;
3+
4+
describe("runtime", ({test, testSkip}) => {
5+
let test_or_skip =
6+
Sys.backend_type == Other("js_of_ocaml") ? testSkip : test;
7+
8+
let assertRuntime = makeRuntimeRunner(test_or_skip);
9+
assertRuntime("numbers.test");
10+
});

stdlib/runtime/numbers.gr

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1592,22 +1592,38 @@ let numberMod = (x, y) => {
15921592
// incRef x and y to reuse them via WasmI32.toGrain
15931593
Memory.incRef(x)
15941594
Memory.incRef(y)
1595-
let xval = coerceNumberToWasmI64(WasmI32.toGrain(x): Number)
1596-
let yval = coerceNumberToWasmI64(WasmI32.toGrain(y): Number)
1597-
if (WasmI64.eqz(yval)) {
1598-
throw Exception.ModuloByZero
1599-
}
1600-
// We implement true modulo
1601-
if (xval < 0N && yval > 0N || xval > 0N && yval < 0N) {
1602-
let modval = WasmI64.remS(i64abs(xval), i64abs(yval))
1603-
let result = if (modval != 0N) {
1604-
i64abs(yval) - modval * (if (yval < 0N) -1N else 1N)
1595+
if (isFloat(x) || isFloat(y) || isRational(x) || isRational(y)) {
1596+
from WasmF64 use { (==), (/), (*), (-) }
1597+
let xval = coerceNumberToWasmF64(WasmI32.toGrain(x): Number)
1598+
let yval = coerceNumberToWasmF64(WasmI32.toGrain(y): Number)
1599+
let yInfinite = yval == InfinityW || yval == -InfinityW
1600+
if (
1601+
yval == 0.0W || yInfinite && (xval == InfinityW || xval == -InfinityW)
1602+
) {
1603+
newFloat64(NaNW)
1604+
} else if (yInfinite) {
1605+
newFloat64(xval)
16051606
} else {
1606-
modval
1607+
newFloat64(xval - WasmF64.trunc(xval / yval) * yval)
16071608
}
1608-
reducedInteger(result)
16091609
} else {
1610-
reducedInteger(WasmI64.remS(xval, yval))
1610+
let xval = coerceNumberToWasmI64(WasmI32.toGrain(x): Number)
1611+
let yval = coerceNumberToWasmI64(WasmI32.toGrain(y): Number)
1612+
if (WasmI64.eqz(yval)) {
1613+
throw Exception.ModuloByZero
1614+
}
1615+
// We implement true modulo
1616+
if (xval < 0N && yval > 0N || xval > 0N && yval < 0N) {
1617+
let modval = WasmI64.remS(i64abs(xval), i64abs(yval))
1618+
let result = if (modval != 0N) {
1619+
i64abs(yval) - modval * (if (yval < 0N) -1N else 1N)
1620+
} else {
1621+
modval
1622+
}
1623+
reducedInteger(result)
1624+
} else {
1625+
reducedInteger(WasmI64.remS(xval, yval))
1626+
}
16111627
}
16121628
}
16131629

0 commit comments

Comments
 (0)