29

Suppose we have 1 and this number in base 2 is:

00000000000000000000000000000001

Now I want to flip all bits to get following result:

11111111111111111111111111111110

As far as I know, the solution is to use the ~ (bitwise NOT operator) to flip all bits, but the result of ~1 is -2:

console.log(~1); //-2
console.log((~1).toString(2)); //-10 (binary representation)

Why do I get this strange result?

Antonio
  • 17,405
  • 10
  • 78
  • 178
Afshin Mehrabani
  • 28,405
  • 26
  • 117
  • 186
  • 42
    `11111111111111111111111111111110` is `-2` – zerkms Jul 13 '15 at 07:21
  • 1
    `11111111111111111111111111111111` is `-1` > `~0 === -1` – Cerbrus Jul 13 '15 at 07:21
  • `00000000000000000000000000000000` is `0` – zerkms Jul 13 '15 at 07:22
  • @zerkms but I get `parseInt(11111111111111111111111111111110, 2) == 1` – Afshin Mehrabani Jul 13 '15 at 07:23
  • 2
    @AfshinMehrabani that's right, because `parseInt` first argument must be a string. `parseInt('11111111111111111111111111111110', 2) | 0` – zerkms Jul 13 '15 at 07:23
  • 3
    [Bitwise operators](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. Two's complement format means that a number's negative counterpart (e.g. 5 vs. -5) is all the number's bits inverted (bitwise NOT of the number, a.k.a. ones' complement of the number) plus one." – Andreas Jul 13 '15 at 07:24
  • @zerkms ops!! what a mistake. Thank you man – Afshin Mehrabani Jul 13 '15 at 07:25
  • There's a great description here of Two's Complement: http://stackoverflow.com/questions/1049722/what-is-2s-complement – xkickflip Jul 13 '15 at 07:26
  • 5
    Add two to it by hand on paper, you'll see it becomes zero. And the number such that adding two to it results in zero should be called minus two. – harold Jul 13 '15 at 10:47
  • Reopened. The OP knows how the operator works. He's just seeing some results he didn't expect. – Cerbrus Jul 14 '15 at 05:49
  • As long as `0b11111111111111111111111111111110` is `-2` this is correct behavior, what result do you expect??? – ST3 Jul 16 '15 at 09:10
  • Possible duplicate of [why is 2 with the bitwise NOT operator -3? it should be -2?](https://stackoverflow.com/questions/58157115/why-is-2-with-the-bitwise-not-operator-3-it-should-be-2) –  Sep 29 '19 at 16:55
  • @SapphireBrick I think you should mark the other question a duplicate of this one. This has better information. – S.S. Anne Sep 29 '19 at 18:21
  • then if that's true, it's not a duplicate. –  Oct 04 '19 at 17:39

7 Answers7

61

There are 2 integers between 1 and -2: 0 and -1

1   in binary is 00000000000000000000000000000001
0   in binary is 00000000000000000000000000000000
-1 in binary is 11111111111111111111111111111111
-2 in binary is 11111111111111111111111111111110
("binary" being 2's complement, in the case of a bitwise not ~ )

As you can see, it's not very surprising ~1 equals -2, since ~0 equals -1.

As @Derek explained, These bitwise operators treat their operands as a sequence of 32 bits. parseInt, on the other hand, does not. That is why you get some different results.


Here's a more complete demo:

for (var i = 5; i >= -5; i--) {
  console.log('Decimal: ' + pad(i, 3, ' ') + '  |  Binary: ' + bin(i));
  if (i === 0)
    console.log('Decimal:  -0  |  Binary: ' + bin(-0)); // There is no `-0`
}

function pad(num, length, char) {
  var out = num.toString();
  while (out.length < length)
    out = char + out;
  return out
}

function bin(bin) {
  return pad((bin >>> 0).toString(2), 32, '0');
}
.as-console-wrapper { max-height: 100% !important; top: 0; }
Cerbrus
  • 60,471
  • 15
  • 115
  • 132
  • 3
    Cool. one more question, if `11111111111111111111111111111110` is `-2`, then why `parseInt('11111111111111111111111111111110', 2)` is `4294967294`? – Afshin Mehrabani Jul 13 '15 at 07:28
  • *FYI* In JavaScript, numbers are represented in signed two's complement. – TaoPR Jul 13 '15 at 07:39
  • 4
    @AfshinMehrabani Bitwise operators only [treat operands as 32 bit integer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators). Not surprisingly that is why you get `-2` when `~`ing it but `4294967294` when parsing. – Derek 朕會功夫 Jul 13 '15 at 07:41
  • 2
    @AfshinMehrabani that's the difference between signed and unsigned integers. – Florian Wendelborn Jul 13 '15 at 08:10
  • 3
    Mathematically, in each row above where it says "in base 2" should really say "in 2's complement". -2 in base 2 is -10, only in computers does there have to be a way to store the sign. Plus there are unsigned data types that are still base 2 but of course are not 2's complement. – Todd Wilcox Jul 13 '15 at 12:53
  • 2
    @ToddWilcox: Mathematically, it's not just two's-complement. To subtract one from any number, scan from the right, changing zeroes to ones, until a one is encountered. Change that to zero and stop. Subtracting one from zero will cause all zeroes to turn to ones. Two's-complement means that the MSB of a number should be extended to all bits to the left. One's-complement means it should be extended to all bits to the left *and* right (note that in binary, 0.11111111...(endlessly) is arbitrarily close to 1, so ....11111111.11111111..... (endless in both directions) is arbitrarily close to 0. – supercat Jul 13 '15 at 20:03
19
100 -4
101 -3
110 -2
111 -1
000  0
001  1
010  2
011  3

A simple way to remeber how two's complement notation works is imagine it's just a normal binary, except its last bit corresponds to the same value negated. In my contrived three-bit two's complement first bit is 1, second is 2, third is -4 (note the minus).

So as you can see, a bitwise not in two's complement is -(n + 1). Surprisingly enough, applying it to a number twice gives the same number:

-(-(n + 1) + 1) = (n + 1) - 1 = n

It is obvious when talking bitwise, but not so much in its arithmetical effect.

Several more observations that make remebering how it works a bit easier:

Notice how negative values ascend. Quite the same rules, with just 0 and 1 swapped. Bitwise NOTted, if you will.

100 -4  011 - I bitwise NOTted this half
101 -3  010
110 -2  001
111 -1  000
----------- - Note the symmetry of the last column
000  0  000
001  1  001
010  2  010
011  3  011 - This one's left as-is

By cycling that list of binaries by half of the total amount of numbers in there, you get a typical sequence of ascending binary numbers starting at zero.

-  100 -4  \
-  101 -3  |
-  110 -2  |-\  - these are in effect in signed types
-  111 -1  / |
*************|
   000  0    |
   001  1    |
   010  2    |
   011  3    |
*************|
+  100  4  \ |
+  101  5  |-/  - these are in effect in unsigned types
+  110  6  |
+  111  7  /
D-side
  • 8,408
  • 3
  • 23
  • 43
14

In computer science it's all about interpretation. For a computer everything is a sequence of bits that can be interpreted in many ways. For example 0100001 can be either the number 33 or ! (that's how ASCII maps this bit sequence).

Everything is a bit sequence for a computer, no matter if you see it as a digit, number, letter, text, Word document, pixel on your screen, displayed image or a JPG file on your hard drive. If you know how to interpret that bit sequence, it may be turned into something meaningful for a human, but in the RAM and CPU there are only bits.

So when you want to store a number in a computer, you have to encode it. For non-negative numbers it's pretty simple, you just have to use binary representation. But how about negative numbers?

You can use an encoding called two's complement. In this encoding you have to decide how many bits each number will have (for example 8 bits). The most significant bit is reserved as a sign bit. If it's 0, then the number should be interpreted as non-negative, otherwise it's negative. Other 7 bits contain actual number.

00000000 means zero, just like for unsigned numbers. 00000001 is one, 00000010 is two and so on. The largest positive number that you can store on 8 bits in two's complement is 127 (01111111).

The next binary number (10000000) is -128. It may seem strange, but in a second I'll explain why it makes sense. 10000001 is -127, 10000010 is -126 and so on. 11111111 is -1.

Why do we use such strange encoding? Because of its interesting properties. Specifically, while performing addition and subtraction the CPU doesn't have to know that it's a signed number stored as two's complement. It can interpret both numbers as unsigned, add them together and the result will be correct.

Let's try this: -5 + 5. -5 is 11111011, 5 is 00000101.

  11111011
+ 00000101
----------
 000000000

The result is 9 bits long. Most significant bit overflows and we're left with 00000000 which is 0. It seems to work.

Another example: 23 + -7. 23 is 00010111, -7 is 11111001.

  00010111
+ 11111001
----------
 100010000

Again, the MSB is lost and we get 00010000 == 16. It works!

That's how two's complement works. Computers use it internally to store signed integers.

You may have noticed that in two's complements when you negate bits of a number N, it turns into -N-1. Examples:

  • 0 negated == ~00000000 == 11111111 == -1
  • 1 negated == ~00000001 == 11111110 == -2
  • 127 negated == ~01111111 == 10000000 == -128
  • 128 negated == ~10000000 == 01111111 == 127

This is exactly what you have observed: JS is pretending it's using two's complement. So why parseInt('11111111111111111111111111111110', 2) is 4294967294? Well, because it's only pretending.

Internally JS always uses floating point number representation. It works in a completely different way than two's complement and its bitwise negation is mostly useless, so JS pretends a number is two's complement, then negates its bits and converts it back to floating point representation. This does not happen with parseInt, so you get 4294967294, even though binary value is seemingly the same.

gronostaj
  • 2,143
  • 2
  • 23
  • 39
6

A 2's complement 32 bit signed integer (Javascript insists that is the format used for a 32 bit signed integer) will store -2 as 11111111111111111111111111111110

So all as expected.

Bathsheba
  • 220,365
  • 33
  • 331
  • 451
5

It's two's complement arithmetic. Which is the equivalent of "tape counter" arithmetic. Tape recorders tended to have counters attached (adding machines would likely be an even better analogy but they were obsolete already when 2s complement became hip).

When you wind backwards 2 steps from 000, you arrive at 998. So 998 is the tape counter's 10s complement arithmetic representation for -2: wind forward 2 steps, arrive at 000 again.

2s complement is just like that. Wind forward 1 from 1111111111111111 and you arrive at 0000000000000000, so 1111111111111111 is the representation of -1. Wind instead back another 1 from there, and you get 1111111111111110 which then is the representation of -2.

4

This is the expected behavior. According to mdn:bitwise-not.

The part you probably don't understand is that [11111111111111111111111111111110]₂ = [10]₂¹, if expressed as a signed integer. The leading 1s can be as many as you want and it's still the same number, similar to leading 0s in unsigned integers/decimal.

¹ [10]₂ specifies that 10 should be interpreted as base 2 (binary)

Florian Wendelborn
  • 1,389
  • 16
  • 27
4

Numbers in JavaScript are floating point numbers, stored and represented by IEEE 754 standard.

However, for bitwise operations, the operands are internally treated as signed 32-bit integers represented by two's complement format:

The operands of all bitwise operators are converted to signed 32-bit integers in two's complement format. Two's complement format means that a number's negative counterpart (e.g. 5 vs. -5) is all the number's bits inverted (bitwise NOT of the number, a.k.a. ones' complement of the number) plus one.

A negative number's positive counterpart is calculated the same way. Thus we have:

 1 = 00000000000000000000000000000001b
~1 = 11111111111111111111111111111110b
     11111111111111111111111111111110b = -2

Note that Number.toString() is not supposed to return the two's complement representation for base-2.

The expression (-2).toString(2) yields -10 which is the minus sign (-) followed by base-2 representation of 2 (10).

Salman A
  • 229,425
  • 77
  • 398
  • 489