[stdlib] Support nextafter at compile time - #6787
#6787[stdlib] Support nextafter at compile time#6787VihaanAgarwal wants to merge 1 commit intomodular:mainmodular/modular:mainfrom VihaanAgarwal:fix/nextafter-comptimeVihaanAgarwal/modular:fix/nextafter-comptimeCopy head branch name to clipboard
nextafter at compile time#6787Conversation
`nextafter` dispatched only to libc `nextafter`/`nextafterf` through an `external_call`, which the comptime interpreter cannot run, so any call in a comptime context failed to compile. That also blocked `math.ulp`, which relies on `nextafter`. This adds a comptime-interpreter branch that walks to the neighbouring value straight from the IEEE-754 bit pattern and keeps the libc dispatch for runtime. Adjacent representable floats of the same sign have adjacent integer bit patterns, so the step toward `arg1` is a single increment or decrement, with signed zero and NaN handled explicitly. Fixes modular#6721 Assisted-by: Claude
|
All contributors have signed the CLA ✍️ ✅ |
|
I have read the CLA Document and I hereby sign the CLA |
| # comptime interpreter cannot execute; these evaluate the branches at | ||
| # compile time to guard the pure-Mojo fallback. | ||
| comptime up = nextafter(Float64(1), Float64(2)) | ||
| assert_almost_equal(up, 1.0000000000000002) |
There was a problem hiding this comment.
These assertions are weaker than they look. assert_almost_equal defaults to atol=1e-8, rtol=1e-5, but nextafter(1, 2) = 1.0000000000000002 differs from 1.0 by only ~2.2e-16 — well inside the tolerance. So this would still pass if the function returned exactly 1.0 (a no-op bug), and likewise the subnormal cases (5e-324 is "almost equal" to 0). Since the whole point of nextafter is the 1-ULP step, please use assert_equal here for exact-bit checking. The test still serves its primary purpose (proving comptime evaluation compiles), so this is a strengthening suggestion rather than a correctness problem — note test_ulp_at_comptime already uses assert_equal, which is the pattern to follow. (The pre-existing runtime test_nextafter has the same shortcoming).
Linked issue
Fixes #6721
Type of change
Motivation
Calling
nextafterat compile time fails to compile. It only ever dispatches tolibc's
nextafter/nextafterfthrough anexternal_call, and the comptimeinterpreter has no way to execute an external C function, so it bails out with
"failed to run the pass manager". Anyone who reaches
nextafterin acomptimecontext hits this, and it also blocks
math.ulp, which is built onnextafter(that is the path in the linked issue).
What changed
Added a comptime-interpreter branch to
nextafterthat computes the neighbourdirectly from the IEEE-754 bit pattern, leaving the libc dispatch untouched for
runtime. For a fixed sign, consecutive representable floats have consecutive
integer bit patterns, so stepping toward
arg1is one increment or decrement ofthe raw bits. The
arg0 == 0, equal-inputs, and NaN cases are handledexplicitly. This follows the same
__is_run_in_comptime_interpreterpatternalready used elsewhere in
math.mojo, and the branch folds away at runtime sothere is no runtime cost or behaviour change.
Testing
./bazelw test //mojo/stdlib/test/utils:test_numerics.mojo.test //mojo/stdlib/test/math:test_math.mojo.test— both pass.test_nextafter_at_comptimecovering the up/down, equal,zero-to-subnormal, negative, vector, and NaN branches.
test_ulp_at_comptimeso the reporter's originalulpscenario isexercised at compile time.
nextafteracross a largesweep of random
float32bit patterns (infinities, subnormals, andMAX_FINITEincluded); every result matched bit for bit.Checklist
Assisted-by:trailer is in the commit message