This question already has an answer here:
-
What does ~~ (“double tilde”) do in Javascript?
9 answers
-
`Math.trunc` vs `|0` vs `<<0` vs `>>0` vs `&-1` vs `^0`
2 answers
I understand that ~ is a bitwise NOT operator, but how does inverting the bits on a number twice make it function as Math.floor
What does ~~ ("double tilde") do in Javascript? describes the differences between using Math.floor vs bitwise operations to round numbers in Javascript, but I am interested in how exactly inverting the bits twice accomplishes this.
Thanks
From the spec, Bitwise NOT, ~
- Let
expr
be the result of evaluating UnaryExpression.
- Let
oldValue
be ToInt32(GetValue(expr))
.
- Return the result of applying bitwise complement to
oldValue
. The result is a signed 32-bit integer.
Definition of ToInt32
here.
The "complement" of a 32-bit integer i
is i XOR 0xFFFFFFFF
.
So put this all together and you have ~~i
as meaning
ToInt32(i) XOR 0xFFFFFFFF XOR 0xFFFFFFFF
// same as
ToInt32(i) XOR 0x00000000
// same as
ToInt32(i)
Keep in mind the differences in rounding direction for negative numbers.
Personally I prefer using x | 0
over ~~x
because it involves fewer operations for the same result.
~
forces the operand to get cast to an integer. It's a trick to work around the fact that Javascript has no direct way of casting between floating point and integer values. In fact, Javascript doesn't officially have an integer-only type, but implementations are likely to use integers internally for performance.
So ~x
is the bitwise inverse of castToInt(x)
. Then you inverse the bits again to get just castToInt(x)
.
It happens that casting floating point numbers to integer behaves almost like Math.floor
. The most notable difference is that casting rounds towards zero, while Math.floor
rounds towards negative infinity. There are some other differences as well:
js> ~~(-4.5)
-4
js> Math.floor(-4.5)
-5
js> ~~Infinity
0
js> Math.floor(Infinity)
Infinity
js> ~~NaN
0
js> Math.floor(NaN)
NaN
js> Math.floor(1e12)
1000000000000
js> ~~1e12
-727379968 // <- be aware of integer overflows!
It's essentially the equivalent of a truncate function (in the sense that it is casting the float into an integer, which does exactly that), which JavaScript does not have. This is why for negative numbers the behavior is actually closer to Math.ceil
.