Tilde or the Floor? Practical use for JavaScript bitwise operators. (#1)

JavaScript bitwise operators are one of these features of JavaScript that we always forget about. It seems like we assume there is no good use for them in a client-side code. Why would we care about ones and zeros if we're not using binary? Why would we ever care for binary AND, OR, XOR or left/right shift?

I am not going to explain how these operators are affecting binary numbers because there are plenty of resources available covering this topic.

Instead, in a series of articles, I will focus on a practical use for few of them showing how to make your JavaScript code faster, more compact and unfortunately often less readable.

Bitwise NOT - The ~ operator

Apart from "inverting the bits of its operand" bitwise NOT in JavaScript is actually very useful not only when it comes to binary. Firstly, it has a very interesting effect on integers - it converts the integer to -(N+1) value. For example:

~2 === -3; //true
~1 === -2; //true
~0 === -1; //true
~-1 === 0; //true

However, the most practical way of utilizing the power of this operator is to use it as a replacement for Math.floor() function as double bitwise NOT performs the same operation a lot quicker. You can use it, to convert any floating point number to a integer without performance overkill that comes with Math.floor(). Additionally, when you care about minification of your code, you end up using 2 characters (2 tildes) instead of 12.

Example use of double tilde / double bitwise NOT operator:

~~2 === Math.floor(2); //true, 2
~~2.4 === Math.floor(2); //true, 2
~~3.9 === Math.floor(3); //true, 3

Performance difference converting a list of 100000 float numbers to integer:

# Browser Math.floor() Bitwise double NOT ~~
#1 Firefox 7.0.1 42ms 29ms
#2 Firefox 7.0.1 44ms 28ms
#3 Chrome 15 63ms 64ms
#4 Chrome 15 63ms 68ms
#5 IE8 265ms 192ms
#6 IE8 324ms 190ms

This test is also available here: http://jsperf.com/jsfvsbitnot.

Performance difference between Math.floor() and JavaScript bitwise double NOT

I must admit these results are very surprising - Internet Explorer actually performs better than Chrome! Math.floor() is still faster on Internet Explorer than bitwise NOT. All other browsers are indeed behaving as expected - JavaScript bitwise NOT performs a lot better.


Use double bitwise NOT when:

1. You want to convert the number from float to integer.
2. You want to perform same operation as Math.floor() but a lot quicker.
3. You want to minimalize your code.

Do not use double bitwise NOT when:

1. You run Google Chrome (apparently?).
2. You care about readability of your code.


In next article I will describe binary left shift and right shift and how to practially use them in your JavaScript code.



When using bit operations to remove decimals, both ~~n and any of the more traditional ways like (n|0) or (n>>0), the operations isn't equivalent to Math.floor, which rounds down, but rather it rounds towards zero (commonly known as "truncating" rounding).
That's actually great since JavaScript doesn't have a Math.trunc function, and there isn't any other (simple) way to get that functionality (because (n < 0 ? Math.ceil(n) : Math.floor(n)) isn't simple :).

Also worth noticing that it only works for numbers in where the truncation is a 32-bit integer.

Thanks for posting this. I don't quite understand floating point representation well enough to see how two sequential bitwise inversions effectively recasts the object as an integer. Do you have any good reference materials that explains why we can do this neat trick? Thanks!

Perhaps the reason the results are so surprising is that jsperf numbers are measured in "operations per second (higher is better)", so "Internet Explorer actually performs better than Chrome!" is the opposite of true.

thanks Tim for pointing this out, I can't believe I missed this important detail!

It appears that since Chrome 25 the V8 is actually doing a ~~number when Math.floor is called. (See the linked jsperf test above)

that'd be too bad since `~~` rounds towards zero and converts to a 32-bit Integer. How would you correctly floor number in range [Math.pow(2, 32), Number.MAX_VALUE] without x - ( x %1 ) which i think might also lead to wrong results in some cases because of the limits of ieee's float's bias and mantisse. not sure on that last one though.

Thanks for this explanation. I only knew about the ~ to -(N+1) (which is great when dealing with Array.indexOf).

By the way, Chrome 29 seems to greatly fix the problem, hence it improved gradualy its performance since Chrome 21.

The ~ operator is also useful in combination with String.indexOf.
Instead of testing for == -1 or >= 0 you can do the following:

var s = "foo";
if (!~s.indexOf("boo")) console.log("no boo in foo");
if (~s.indexOf("oo")) console.log("woohoo");

re: `~~2 === Math.floor(2); //true, 3`
nope! should be `//true, 2`


Plain text

  • No HTML tags allowed.
  • Lines and paragraphs break automatically.
This question is for testing whether you are a human visitor and to prevent automated spam submissions.

Check out my latest game!

Android app on Google Play

Recent comments