Skip to content

fix(runtime): return max/2 and min/2 arguments unchanged - #8

Merged
mremond merged 1 commit into
mainfrom
fm/task-84ad68a5
Nov 12, 2026
Merged

mremond merged 1 commit into
mainfrom
fm/task-84ad68a5

Conversation

@mremond

@mremond mremond commented Nov 12, 2026

Copy link
Copy Markdown
Member

Why

Natively, erlang:max/2 and min/2 turned the integer of a mixed integer and float pair into a new float, so max(2, 1.5) returned 2.0 and max(1, 1.0) returned 1.0, where BEAM returns 2 and 1.

Rounding the integer first also made integers above 2^53 compare equal to smaller floats.

What changed

  • max/2 and min/2 return one of their two arguments unchanged, the first when they compare equal, as on BEAM.
  • Every pair goes through the term order, which already compares integers and floats exactly, so the separate numeric arms are gone.
  • The new max_min_numbers fixture covers mixed pairs, signed zeros, values around 2^53, 2^60, 2^63 and beyond 64 bits, and direct, apply, fun and guard calls; it runs natively and on BEAM.
  • The runtime unit test that expected max(3, 2.5) to be 3.0 now expects the integer itself; the changelog and the tests README describe the change.

Before and after

Before: Natively, max(2, 1.5) was 2.0, min(1, 1.0) was 1.0, and max(9007199254740993, 9007199254740992.0) was a float.

After: Natively, max(2, 1.5) is 2, min(1, 1.0) is 1, and max(9007199254740993, 9007199254740992.0) is the integer, as on OTP 29.

The change is in runtime results that a program prints or matches, not in anything drawn on screen.

Checked

  • A probe of 37 mixed integer, float and atom pairs through max, min, apply and fun calls, printed with exact float bits, natively and on OTP 29.1.2: 18 lines differed from OTP before the change; the native output is byte-identical to OTP after it
  • The max_min_numbers fixture with the compiler built from the base version: Fails at its first line with {badmatch,2.0}
  • The max_min_numbers fixture after the change, ten runs natively and ten on OTP 29.1.2: 10 of 10 pass on each
  • Timing of max/2 over 20 million calls, eight interleaved runs before and after: Integer pairs about 4.3 to 4.5 ns per call either way; differences sit within this busy machine's noise
How it was checked

cargo build --release --locked -p nera-runtime, then -p nera, then the whole workspace: passed

cargo clippy --workspace --all-targets --locked -- -D warnings: passed

cargo test --workspace --locked --no-fail-fast: 2,139 passed, 0 failed, 1 ignored

python3 scripts/test-allocator-config.py: passed

nera test -j 1 --timeout 30: 467 passed, 0 failed, 2 skipped (external OneCached modules), max_min_numbers included

cargo test -p nera-runtime --lib bif::tests::bif_m ten times: 10 of 10, five tests each

nera test max_min_numbers ten times: 10 of 10; the same fixture under erl on OTP 29.1.2 ten times: 10 of 10

Checked on macOS arm64 only

Risk

Risk: low in every dimension.

Full risk assessment
Dimension Level Why
Blast radius low One runtime helper behind erlang:max/2 and min/2
Behavior low Corrects a result that differed from BEAM
Undo low Revert the commit
Evidence low Native output matches OTP 29 on 37 pairs; fixture runs natively and on BEAM
Sensitivity low No settings, data or sign-ins
Novelty low Reuses the existing exact term comparison
Dependencies low No dependencies added or updated

erlang:max/2 and min/2 now return one of their two arguments as it was,
the first when the two compare equal, as on BEAM: max(2, 1.5) is 2 and
max(1, 1.0) is 1. The mixed integer and float arms rounded the integer
to a float and returned a new float, which also lost precision above
2^53. Every pair now goes through the term order, which compares
integers and floats exactly.

The max_min_numbers fixture runs natively and on BEAM.
@mremond
mremond merged commit dfe6979 into main Nov 12, 2026
40 of 42 checks passed
@mremond mremond added this to the 0.1.1 milestone Nov 12, 2026
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