The previous implementation intercepted `n / 0` and `n % 0` and returned
`.null`, on the rationale that `null` is friendlier for rule authors and
matches the engine's "missing value" convention. That's the last
remaining intentional deviation from `json-logic-js` in this file —
remove it for full spec parity.
`json-logic-js` delegates `/` and `%` to native JS arithmetic, which
follows IEEE 754: `n / 0` is `±Infinity` (sign matches the dividend),
`0 / 0` is `NaN`, and any `n % 0` is `NaN`. Swift's `Double` operators
and `truncatingRemainder(dividingBy:)` already produce these IEEE
values, so the fix is just dropping the divisor==0 short-circuit and
wrapping the result in `.float`.
Tests:
- Replace `testDivByZeroReturnsNull` with `testDivByZeroFollowsIeee754`
pinning all three cases (`+Infinity`, `-Infinity`, `NaN`).
- Replace `testModByZeroReturnsNull` with `testModByZeroIsNan` covering
`7 % 0` and `0 % 0`.
- Update the `1 / null` / `1 % null` cases inside
`testSubDivAndModUseToNumberPerSpec` (divisor coerces to 0 →
`+Infinity` / `NaN` instead of `.null`).
Co-authored-by: Cursor <cursoragent@cursor.com>
`json-logic-js` is asymmetric about which JS coercion arithmetic uses:
`+` and `*` go through `parseFloat(value)` (stringify, parse longest
numeric prefix); `-`, `/`, `%` go through native arithmetic which calls
`Number(value)` (a.k.a. `ToNumber`). The prior implementation routed
every operand through `Value.asNumber` (a partial `ToNumber`), which
diverged from the spec in two directions:
- For `+` / `*`: `null`, bools, the empty string, and `[1]` produced
numbers instead of `NaN`.
- For `-` / `/` / `%`: arrays produced `NaN` instead of being coerced
via `ToPrimitive("number")` → `toString` → recurse, so `[] - 1` and
`[1] - 1` were `NaN` instead of `-1` and `0`.
Add `jsString(value)` (top-level JS `String()`) and `jsParseFloat(value)`
to `Value.swift`, extend `Value.asNumber` to coerce arrays/objects via
`jsString`, and route `+` / `*` through `jsParseFloat` while leaving
`-` / `/` / `%` on `asNumber ?? .nan`. The existing
divide-/modulo-by-zero short-circuit (`.null` instead of `±Infinity` /
`NaN`) is the only remaining intentional deviation in this file and is
documented as such.
Tests:
- Replace `testNullOperandIsTreatedAsZero` with two coercion-path tests
that pin both branches against the spec, including the array cases
(`[1] + 1 = 2`, `[1,2] + 0 = 1`, `[] - 1 = -1`, `[1] - 1 = 0`,
`[1,2] - 0 = NaN`).
- Update `testAddOneArgActsAsNumericCast` and split
`testAddCoercesStringsAndBools` into `testAddCoercesNumericStrings`
to reflect that bools no longer bridge through `+` / `*`
(`parseFloat("true") === NaN`) but trailing junk in numeric strings
now parses (`parseFloat("3.14abc") === 3.14`).
Co-authored-by: Cursor <cursoragent@cursor.com>