Sunday, August 28, 2016

The double-tilde (~~x) technique in JavaScript - What it is, and why you should be careful about using it.

Watch



What is it?

The ~ (tilde) operator in JavaScript is the often-overlooked bit-wise not. For integers, it is the equivalent to running:

~x == -(x + 1)

So ~(10) will give you -11. There's another caveat to the ~ operator. It casts floating point numbers to integers by dropping everything after the decimal. So in the more general sense,

~x == -(Math.trunc(x) + 1)

Recall that Math.trunc() is kind of like Math.floor(), except the value (positive or negative) will always be rounded towards 0. The ~~ technique is a common trick you'll find a lot of developers using. Let's resolve it:

~~x = -(Math.trunc(-(Math.trunc(x) + 1)) + 1)
-(Math.trunc(-(Math.trunc(x)) - 1) + 1)
-(Math.trunc(-(Math.trunc(x))) + 1 - 1)
-(Math.trunc(-(Math.trunc(x))))
= --Math.trunc(x)
Math.trunc(x)

Essentially, ~~ is used as a short-hand hack for Math.trunc(). 

But you should probably never use it.

I'm pretty lazy so I use to use this a lot... until it got me in trouble. It turns out that ~~ doesn't work for really large numbers. For instance, when I run ~~1000000000000, the result is -727279968, so in this case not only did we lose a ton of precision, the answer actually has the wrong sign! So if you were expecting a positive number because the input was positive, this type of thing can really wreak havoc, like crashing your Node.js server or whatever.

But even if you understand the way that numbers work in JavaScript and only use the ~~ trick where appropriate, I would still contend that using this technique is detrimental in the same way as goto statements or the xor swap algorithm. It's esoteric, and prone to bugs. It is not beginner-friendly, doesn't come with documentation, and if accepted by convention could lead unknowing developers down the path to buggy code.

Alternative?

Instead of using ~~ just stick with Math.floor(). If you want a way to truncate decimals (flooring both positive and negative numbers towards 0), add this declaration at the top of your code:

if(!Math.trunc){
    Math.trunc = function(value){
        return Math.sign(value) * Math.floor(Math.abs(value));
    }
}

Note that Math.trunc() is already available in ECMAScript 6 compliant JavaScript engines (which is used by most modern browsers, but not all), and doesn't need to be added in those environments.


2 comments:

  1. "~~(-5) will evaluate to -4"

    Not actually the case. You may be thinking that ~(-5) == 4 which is true, but also expected.

    ReplyDelete
    Replies
    1. Whoa, thanks for the correction. I thought I tested this. Must have made a mistake. Thanks again.

      Delete