JS numbers are floating-point, except when the JIT optimizer can use an integer type inside a loop or function. As such, instructions like x86 minsd
and maxsd
can do the job in one single-uop instruction with good throughput and fairly good latency (https://uops.info/). (More efficiently than for integers in general-purpose registers, ironically, although cmp
/ cmov
is fairly efficient there, too and still only 2 uops instead of 1.)
A 2's complement bithack hides the real logic from the JIT optimizer so you're unlikely to have it turn that mess back into x86 cmp
/ cmov
or AArch64 cmp
/ csel
or whatever. (Or in a simple test where the same input is always the max, branchy code-gen can look good, when it would mispredict in real cases. Of course with constant inputs you'd expect things to optimize away after constant-propagation.)
IDK why floor
would be faster than | 0
. Check the asm to make sure neither optimized away. |0
should be able to use x86 cvttsd2si
(convert (with Truncation) scalar double to signed/scalar integer.
You'd expect a JIT to use SSE4.1 roundsd
for floor
on new-enough x86 CPUs, which is less efficient (2 uops on mainstream Intel CPUs.) Even if the result is used with integer stuff after floor
, rounding toward -Infinity is different from rounding toward 0, so it can't just use cvttsd2si
. If AVX-512 was available, that could allow a rounding-mode override for vcvtsd2si
(use the current rounding mode, or an AVX-512 override) but I doubt that was happening in 2015. Changing the current FP rounding mode for a loop would be possible, but unlikely.
Both are of course no-ops if the value is already an integer.