29

I'm generally not a fan of microbenchmarks. But this one has a very interesting result.
http://ernestdelgado.com/archive/benchmark-on-the-floor/

It suggests that Math.floor is the SLOWEST way to calculate floor in Javascript. ~~n, n|n, n&n all being faster.
This seems pretty shocking as I would expect that people implementing Javascript in today's modern browsers would be some pretty smart people.

Does floor do something important that the other methods fail to do? Is there any reason to use it?

Mark Bolusmjak
  • 23,606
  • 10
  • 74
  • 129
  • 4
    `~~n`, `n|n` and `n&n` does not produce the same result as `Math.floor`. The first three can only return 32-bit integers. Try `n = 50000000000.4`. – kennytm Mar 26 '10 at 21:00
  • In my browser (FF 3.6.something) they were all the same speed in the results (about "3", whatever that means). Try it on different browsers (the little test is provided at the bottom) instead of just taking the results at face-value. There really is no reason why it should be noticeably slower. –  Mar 26 '10 at 21:02
  • 4
    Actually, running his benchmark in FF 3.6 shows that `Math.floor` is the fastest – Justin Johnson Mar 26 '10 at 21:56
  • 1
    They don't behave the same same as `Math.floor` - they're the same as `Math.trunc`, which is different for negative non-integers. – Grumdrig Jan 06 '17 at 21:13
  • 1
    The link doesn't work, here is an updated benchmark: https://jsperf.app/js-floors/3 – MarcellPerger Apr 11 '23 at 17:38

2 Answers2

36

The primary reason Math.floor is slower (where it actually is--in some tests I've done it's faster) is that it involves a function call. Older JavaScript implementations couldn't inline function calls. Newer engines can inline the call, or at least make the property lookup faster, but they still need a guard condition in case you (or some other script) overwrote the Math.floor function. The overhead is minimal though, so there's not much difference in speed.

More importantly though, as was mentioned in several comments, the other methods are not equivalent. They all work by doing bitwise operations. The bitwise operators automatically convert their operands to 32-bit integers by truncating the number. That's fine if the number fits in 32 bits, but JavaScript numbers are 64-bit floats, which could be much larger than 2147483647.

They also give a different result for negative numbers, since converting to integers truncates and Math.floor always rounds down. For example, Math.floor(-2.1) === -3, but (-2.1) | (-2.1) === -2.

If you know you are only dealing with positive numbers less than 2147483648, and you need to squeeze every bit of performance out of your code in older browsers (Make sure it's actually the bottleneck first. It probably isn't.), I would use an even simpler method: x|0. It doesn't evaluate the variable twice, and it works even if x is an expression (just be sure to put it in parentheses so you don't run into precedence issues).

Matthew Crumley
  • 101,441
  • 24
  • 103
  • 129
  • "JavaScript can't inline functions because it is dynamic and you could replace Math.floor with a different function." That is a non sequitur. JIT compilers can and do inline dynamic function calls. Tracing JITs are especially good at this. The dynamism is handled either by guard checks or adding invalidation triggers. A specific implementation might or might-not inline. So as with all micro-optimizations, if it really does matter, measure it on your target platforms. – Ants Aasma Mar 29 '10 at 16:23
  • @Ants: Good point. That's what I was getting at with the cached function values, but I worded it incorrectly. – Matthew Crumley Mar 29 '10 at 18:09
  • 6
    I know this answer is a couple years old, but V8 inlines function calls now, and this results in [`Math.floor` performing as well as bitwise hacks](http://jsperf.com/js-floors). – josh3736 Jun 05 '12 at 01:58
27

It has nothing to do with modern browsers. It has to do with implementing the ECMA standard. You can't just change how a certain function performs even if there is a faster way. It could break existing code.

The Math.Floor has to account for a lot of different scenarios of handling different types. Could they have made different scenarios faster by taking short cuts as you described? Maybe they could, but that might have broken other scenarios. Just because something on the surface looks small, doesn't mean that there isn't an iceberg underneath.

Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
kemiller2002
  • 113,795
  • 27
  • 197
  • 251
  • 7
    +1 Slow or not, it's probably the only thing doing it *correctly* – Josh Stodola Mar 26 '10 at 20:59
  • 1
    Good point. `Math.floor("foo")` returns `NaN`. But if that is the only benefit I'm tempted to use another method. At least until Javascript supports integer division. – Mark Bolusmjak Mar 26 '10 at 21:01
  • I'm not saying that you should always use Math.Floor, it is slow. It's just that when they design a function they have to account for a lot of different scenarios people might do. One thing about JS is that it's meant to be easy to program with which means they had to take extra precautions when building it and in some scenarios made them take the long way around. – kemiller2002 Mar 26 '10 at 21:04
  • @z5h - Another benefit is that `Math.floor()` correctly rounds down for negative numbers, while most of the bitwise operators round towards 0 (although `>>>` goes completely off the charts). – Ted Hopp Jan 08 '17 at 20:30