I want to share a tiny character-saving trick I use when flooring a number towards zero in JS. Hopefully you’re aware of the following idiom:
Don’t worry… this post isn’t all about ~~…
In most cases this is used instead of
Math.floor because it’s faster, terser and most important, much more confusing! Be careful though, it has different behaviour to
Math.floor. For most cases, it has the effect of flooring the number towards zero. I wrote a post covering it in some detail a while ago.
It’s important to note, as pointed out by Mathias Bynens, that this and other similar bitwise operations only work reliably on numbers that can be expressed as 32-bit sequences. Any numbers above 2147483647 or below -2147483648 will not work as you expect. This is usually acceptable though.
The actual trick which I wanted to post about relates to operator precedence. I often find myself in a situation like this:
// Generate random RGB red value: var r = ~~(Math.random() * 255);
The bitwise-not operator (
`~`) has a higher precedence than the multiplication operator (
`*`) so the parentheses around
`Math.random() * 255` are necessary to avoid the
`~~` only applying to
// This doesn't work correctly var r = ~~Math.random() * 255;
Fortunately, there are other similarly effective bitwise operations that have the same flooring effect, but with lower operator precedence. One of them is
`0 | n`. The pipe operator (bitwise OR) has a lower precedence than the multiplication operator so we can use it without the need for parentheses:
var r = 0 | Math.random() * 255;
So we can save a couple of characters! Joy!
There’s also the bitwise shift operators with similarly low precedence, e.g.
var r = Math.random() * 255 >> 0; var r = Math.random() * 255 << 0; var r = Math.random() * 255 >>> 0;
Just to be clear, with all these strange bitwise flooring operations, we’re not interested in manipulating the bits of the 32-bit sequence at all. All we’re interested in is getting the integer produced by the internal
`ToInt32` operation, hence why all bitwise flooring techniques are no-op bitwise operations: e.g. we bitwise-not and then bitwise-not again, causing a reversal of its effects (comparable to
`!!true`). We bitwise-or with zero as an operand, meaning that for every bit-pair, the non-zero operand will win and thus nothing will change… and it’s the same deal with
`n << 0` and
`n & n`...
What's important is that accessing the internal ToInt32 via bitwise operations is much faster than via the
`parseInt` function, as is clearly evident in Mathias' test case.
... and now for the "revelation":
Regarding the usage of these tricks: over time I've come to the opinion that they're almost entirely pointless. The performance differences, even though they may seem staggering, are entirely negligible in most cases. You should only sacrifice readability for performance if you really need to squeeze out those precious microseconds. The only other reason for using these bitwise tricks is to make your code look more complicated than it really is, which is probably a stupid reason. As mentioned, there are also edge cases to watch out for. You can't just replace all your current
`Math.floor()` calls with
`~~`. Things will likely fail. What's worse is that your test coverage probably isn't exhaustive enough to catch these edge cases!
Even though I shouldn't, I still use them though. They're "cool"...