Simplify and tune [l]lround[f] - #62
Open
leekillough wants to merge 1 commit into
Open
Conversation
Author
|
All CI tests pass. |
leekillough
force-pushed
the
lround
branch
2 times, most recently
from
July 10, 2026 21:14
d62dec6 to
7804613
Compare
leekillough
force-pushed
the
lround
branch
3 times, most recently
from
July 29, 2026 00:36
49f2d83 to
7d01508
Compare
used a custom multi-branch bit-manipulation algorithm (~100 lines each) to perform round-half-away-from-zero. `lroundf.c` and `lround.c` had a dead-code Windows path guarded by `#ifdef WIN64` (never defined; the compiler defines `_WIN64`), so the Linux code path always ran on Windows. `llround.c` and `llroundf.c` were guarded by `#ifdef WINDOWS` (also never defined), causing them to forward all calls to `FN_PROTOTYPE(lroundf)` and `FN_PROTOTYPE(lround)` through the dispatch table. This PR replaces these four files with correct, portable ~20-line implementations.
leekillough
force-pushed
the
lround
branch
2 times, most recently
from
August 6, 2026 00:14
4da35ff to
b0f2853
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Simplify and Fix
lround,lroundf,llround,llroundfSummary
The previous implementations of
lroundf,llround, andllroundfused a custom multi-branch bit-manipulation algorithm (~100 lines each) to perform round-half-away-from-zero.lroundf.candlround.chad a dead-code Windows path guarded by#ifdef WIN64(never defined; the compiler defines_WIN64), so the Linux code path always ran on Windows.llround.candllroundf.cwere guarded by#ifdef WINDOWS(also never defined), causing them to forward all calls toFN_PROTOTYPE(lroundf)andFN_PROTOTYPE(lround)through the dispatch table. This PR replaces these four files with correct, portable ~20-line implementations, and adds conformance tests for all four functions.Changes
Files modified:
src/ref/lround.c,src/ref/lroundf.c,src/ref/llround.c,src/ref/llroundf.c.The old algorithm extracted the exponent and significand via union type-punning, conditionally added 0.5 (if not already an integer), then re-extracted and shifted the significand to form the integer result. On Windows it also had a separate path guarded by
#ifdef WIN64(never defined in the AOCL build, which defines_WIN64).The new implementation checks whether the input is in the valid output range, then adds a signed 0.5 bias (constructed from the sign bit of x) and casts to truncate. The cast always truncates toward zero regardless of MXCSR, making the result rounding-mode independent. Values with |x| at or above the integrality threshold (2^23 for float, 2^52 for double) are already exact integers and skip the addition, to avoid creating a spurious half-integer that would round wrong for odd integers near those thresholds. Out-of-range inputs (NaN, Inf, overflow) call
feraiseexcept(FE_INVALID)directly and returnLLONG_MIN/LONG_MIN.For example,
llround:lround.candlroundf.cfollow the same structure but withlongas the return type, so the in-range bound comparisons are platform-dependent (strict>on the lower bound for 32-bitlongwhere both bounds are exactly representable;>=for 64-bitlongwhereLONG_MINrounds to itself).llroundf.cfollows the same structure asllround.cusing float bit-pattern constants.Correctness fix for
lround/lroundfThe old
#ifdef WIN64guard was never true. The Linux path always ran on Windows, using a 63-bit overflow limit whenlongis only 32 bits on Windows. Any input in [2^31, 2^63) passed the range check and returned a silently truncated garbage value instead of theLONG_MINerror sentinel.Correctness fix for
llround/llroundfThe old
#ifdef WINDOWSguard was never true, sollroundandllroundfalways forwarded tolround/lroundfthrough the function-pointer dispatch table. The return type oflroundislong, which is 32 bits on Windows, sollroundsilently returned a 32-bit value for any input, giving wrong results for inputs outside [-2^31, 2^31) even thoughllroundshould support the full 64-bit range.Range-check bounds
For
llround,LLROUND_MIN = (double)LLONG_MIN - 0.5evaluates to -2^63 (the subtraction of 0.5 is swamped by the 2048 ULP at that magnitude), so -2^63 itself is a valid input that passes the>=check.LLROUND_MAX = (double)LLONG_MAX + 0.5evaluates to 2^63 (LLONG_MAX rounds up to 2^63 in double), which overflows, so a strict<is used on the upper bound.For
lroundfon Windows (32-bitlong), the bounds are constructed at compile time using(float)LONG_MINand(float)LONG_MAX + 0.5fby the same reasoning. The integrality threshold usesEXP_VAL_23_F32(2^23 as a float bit pattern) andEXP_VAL_52_DP64(2^52 as a double bit pattern) fromlibm_util_amd.h.Hot path code generation
Clang compiles the signed-half addition pattern to:
vandpd/vandps(mask the sign bit fromux),vorpd/vorps(inject the 0.5 exponent viaHALFEXPBITS),vaddsd/vaddss(add),vcvttsd2si/vcvttss2si(convert-with-truncation). The range check and integrality check are predicted-not-taken forward branches to the cold path, so the hot path has no taken branches.Performance
Benchmarked on AMD Ryzen 9 9950X (Zen 5, 5 GHz), Windows 11, Clang/LLVM 20 build.
14 seeds × 2 reps = 28 measurements, ABCDDCBA palindrome pattern; median of 28 reported.
Throughput in Mcalls/s; ratios > 1.00 mean PR is faster than the reference.
lroundimproves by 23% because the new correct implementation is faster than the old buggy Linux code path that silently ran on Windows due to the dead#ifdef WIN64guard.lroundfappears to regress at 0.81x, but the DEV baseline always returned 0 on Windows for 32-bitlonginputs due to the same#ifdef WIN64defect, making it artificially fast; the PR is the first correct implementation and the apparent regression is not real.llroundfappears at parity (1.00x), but DEV forwarded to the same buggylroundfthrough the dispatch table (dead#ifdef WINDOWSguard), so the DEV baseline was also artificially fast; the PR is the first correct implementation.llroundis genuinely at parity with DEV.AOCL outperforms Intel on all four functions by 2-3x.
Tests
Conformance tests covering
lround,lroundf,llround, andllroundfare added in theulp_thresholdPR, ingtests/lround/lround_test.ccandgtests/lround/test_lround_data.h. They are separated because the test callbacks require the extended-precision MPFR infrastructure introduced byulp_threshold. The tests use directTEST()cases that call theamd_*entry points, check the return value, and verify thatFE_INVALIDis raised (or not raised) for each case. Test cases include: zero, negative zero, exact integers, round-half-away-from-zero cases (0.5 rounds to 1, -0.5 rounds to -1, 2.5 rounds to 3, -4.5 rounds to -5, etc.), large exact integers (2^52, 2^52+1 for double; 2^23 for float), the largest double and float values representable below 2^63, -2^63 =LLONG_MIN(a valid non-overflowing input), and out-of-range inputs (NaN, sNaN, +/-Inf, +2^63) that must returnLLONG_MINand raiseFE_INVALID.