title: Truncating the fast (but risky) way tip-number: 18 tip-username: pklinger tip-username-profile: https://github.com/pklinger tip-tldr: .~~X
is usually a faster Math.trunc(X)
, but can also make your code do nasty things.
This tip is about performance…with a hidden price tag.
Have you ever come across the double tilde ~~
operator? It’s also often called the “double bitwise NOT” operator. You can often use it as a faster substitute for Math.trunc()
. Why is that?
One bitwise shift ~
first truncates input
to 32 bits, then transforms it into -(input+1)
. The double bitwise shift therefore transforms the input into -(-(input + 1)+1)
making it a great tool to round towards zero. For numeric input, it therefore mimics Math.trunc()
. On failure, 0
is returned, which might come in handy sometimes instead of Math.trunc()
, which returns NaN
on failure.
// single ~console.log(~1337); // -1338// numeric inputconsole.log(~~47.11); // -> 47console.log(~~1.9999); // -> 1console.log(~~3); // -> 3
However, while ~~
is probably a better performer, experienced programmers often stick with Math.trunc()
instead. To understand why, here’s a clinical view on this operator.
~~
is probably faster than Math.trunc()
across the board, though you should test that assumption on whichever platforms matter to you. Also, you’d generally have to perform millions of such operations to have any visible impact at run time.
If you’re trying to confuse others, or get maximum utility from your minifier/uglifier, this is a relatively cheap way to do it.
Code clarity is of great importance in the long term, whether you work in a team, contribute to public code repos, or fly solo. As the oft-quoted saying goes:
Always code as if the person who ends up maintaining your code is a violent psychopath who knows where you live.
For a solo programmer, that psychopath is inevitably “you in six months”.
~~
always rounds to zeroNewbie programmers may fixate on the cleverness of ~~
, forgetting the significance of “just drop the fractional portion of this number”. This can easily lead to fencepost errors (a.k.a. “off-by-one”) when transforming floats to array indices or related ordinal values, where a different kind of fractional rounding may actually be called for. (Lack of code clarity usually contributes to this problem.)
For instance, if you’re counting numbers on a “nearest integer” basis, you should use Math.round()
instead of ~~
, but programmer laziness and the impact of 10 whole characters saved per use on human fingers often triumph over cold logic, leading to incorrect results.