7

I am not an expert in bitwise operators, but i often see a pattern which is used by programmers of 256k demos at competitions. Instead of using Math.floor() function, double bitwise NOT operator is used ~~ ( maybe faster ? ).

Like this:

Math.floor(2.1); // 2
~~2.1 // 2

Search revealed that there are more patterns that used the same way:

2.1 | 0  // 2
2.1 >> 0 // 2

When playing with this in the dev console, i have noticed a behavior that i'm not sure i understand fully.

Math.floor(2e+21);  // 2e+21
~~2e+21;    // -1119879168
2e+21 | 0;  // -1119879168

What is happening under the hood ?

zessx
  • 68,042
  • 28
  • 135
  • 158
Alexander
  • 12,424
  • 5
  • 59
  • 76
  • 8
    [Bitwise operators treat the values as a 32bit integer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators): *"The operands of all bitwise operators are converted to signed 32-bit integers in two's complement format."* Under the hood: http://www.ecma-international.org/ecma-262/5.1/#sec-11.4.8 – Felix Kling Oct 27 '14 at 20:10
  • 1
    As a note, [Programming Puzzles and Code Golf SE](http://codegolf.stackexchange.com/help/on-topic) would an appropriate location for you to explore why `~~` is used. A major benefit of using `~~` as opposed to `Math.floor(n)` is that it saves 11 characters, which is a very large amount of saved bytes when the task is to write as short of a program as possible. – Compass Oct 27 '14 at 20:17
  • possible duplicate of [What does ~~ ("double tilde") do in Javascript?](http://stackoverflow.com/questions/4055633/what-does-double-tilde-do-in-javascript) – Raymond Chen Oct 27 '14 at 21:48

2 Answers2

4

As Felix King pointed out, the numbers are being converted to 32 bit signed integers. 2e9 is less than the maximum positive value of a signed int, so this works:

~~(2e9)  //2000000000

But when you go to 2e10, it can't use all the bits, so it just takes the lowest 32 bits and converts that to an int:

~~(2e10) //-1474836480

You can verify this by using another bitwise operator and confirming that it's grabbing the lowest 32 bits:

2e10 & 0xFFFFFFFF // also -1474836480
~~(2e10 & 0xFFFFFFFF) // also -1474836480

Math.floor is built to account for large numbers, so if accuracy over a big range is important then you should use it.

Also of note: The ~~ is doing truncation, which is the same as flooring for positive numbers only. It won't work for negatives:

Math.floor(-2.1) // -3
~~(-2.1) // -2
Evan M
  • 2,573
  • 1
  • 31
  • 36
2

As stated in the MDN Docs, and here I quote,

The operands of all bitwise operators are converted to signed 32-bit integers in two's complement format.

This means tha when you apply a bitwise operator, for instance ~, to 2.1 it is first converted to an integer, and only then is the operator applied. This effectively achieves the rounding down (floor) effect for positive numbers.

As to why these operators are used, instead of the much nicer to grasp Math.floor, there are two main reasons. For one, these operators may be considerably faster to achieve the same result. Besides performance, some people just want the shortest code possible. All three operators you mentioned achieve the same effect, but ~~ just happens to be the shortest, and arguably the easiest to remember.

Given that the float to integer conversion happens before the bitwise operators are applied, let's see what happens with ~~. I'll represent our target number (2, after the convertion from 2.1) using 8 bits, instead of 32, for shortness.

  2: 0000 0010
 ~2: 1111 1101    (-3)
~~2: 0000 0010

So, you see, we apply an operator to retrieve only the integer part, but we can't apply only one bitwise not, because it would mess up the result. We revert it to the desired value applying the second operator.


Regarding your last example, take into account that the number you're testing with, 2e+21, is a relatively big number. It's a 2 followed by twenty-one zeroes. It simply doesn't fit as a 32-bit integer (the data-type it is being converted to, when you apply the bitwise operators). Just look at the difference between your number and what a 32-bit signed integer can represent.

Max. Integer:             2147483647
       2e+21: 2000000000000000000000

How about binary?

Max. Integer:                                        01111111111111111111111111111111
       2e+21: 11011000110101110010011010110111000101110111101010000000000000000000000

Quite big, huh?

What really happens under the hood is that Javascript is truncating your big number to what it can represent in 32 bits.

110110001101011100100110101101110001011 10111101010000000000000000000000
^----            Junk             ----^

When we convert our truncated number to decimal, we get back what you're seeing.

Bin: 10111101010000000000000000000000
Dec: -1119879168

Conversely, Math.floor accounts for the big numbers and avoids truncating them, which is one of the possible reasons for it being slower, although accurate.

afsantos
  • 5,178
  • 4
  • 30
  • 54