Then you learn that all the “bitwise” operators (~, |, &, ^, <<, >> and >>>) treat their operands as 32-bit integer values and produce 32-bit integer results, and you breathe a sigh of relief.
> n = -1526726656 -1526726656 > n &= 0x80808080 -2147483648 > n == 0x80000000 false > n == -0x80000000 true
With one exception: the unsigned right-shift operator. It does more than simply shift zero bits in from
the left; it also zeros all the bits above the sign bit. This means that
n >>> 0, while leaving the low 32 bits
unchanged, also clears the upper bits, resulting in a value that is positive, albeit outside the signed 32-bit range.
It is equivalent to adding the 33-bit value 0x100000000 to a negative 32-bit number:
> n = (n < 0? n + 0x100000000 : n) 2147483648 > n.toString(16) '80000000'
> n |= 0 -2147483648 > n.toString(16) '-80000000'
the result will be negative again. This is simply how all bitwise operators (except for unsigned right-shift) operate: they truncate the result to a signed 32-bit value.
The safest way to write a 32-bit constant like 0x80000080 is “0x80000080|0”, which will produce -2147483520. If you write all your negative 32-bit constants that way, then you won’t have to resort to using either unsigned right-shifts or 33-bit addition, which in turn avoids the use of floating point values.
To continue the fun, try setting bit 0 of 0x80000000, which should give you 0x80000001:
> n |= 1 -2147483647 > n.toString(16) '-7fffffff'
WTF? Have all the low 32 bits flipped instead?
Actually, no, this time, I’m pulling your leg. The low 32 bits of the internal value are exactly what you would expect: 0x80000001 (the internal representation is more like 0xFFFFF80000001). But as the MDN Docs explain, for a negative number, toString() returns the positive representation of the number, preceded by a - sign, not the “two’s complement” of the number.
October 26, 2014 (Updated September 8, 2015)