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.
Math.floorfaster than the|operator?", and comes down to both browser engine and maybe floor saves a little time by not needing to do a int32 conversion