4

I am trying to create a 32-bit bitmask in JS. However, I don't understand what's happening here:

$ node
> const num = Math.pow(2, 31) - 1
undefined
> num
2147483647

# So far, so good
> num.toString(2)
'1111111111111111111111111111111'
> num.toString(2).length
31

# According to MDN, the left-shift operator does:
# "Excess bits shifted off to the left are discarded. 
#    Zero bits are shifted in from the right."
# But that's not what we see here. Instead, it seems to be wrapping.
> num << 1
-2
> (num << 1).toString(2)
'-10'

According to my understanding of the MDN docs, I'd expect to have a bitmask of 31 1s followed by 1 0. Instead, I get -10. What's going on here?

Nick Heiner
  • 119,074
  • 188
  • 476
  • 699
  • this might help? https://stackoverflow.com/questions/16155592/negative-numbers-to-binary-string-in-javascript – Wreigh Jan 30 '18 at 05:51

3 Answers3

3

Javascript doesn't have Integer, but bitwise operators only make sense on Integer.

So before bitwise operators, javascript will apply ToInt32(val) to your num.

For "Signed 32 bits Integer", the top bit represents 'signed'.

Finally your num overflow the 'signed bit'.

My English is poor, you can check ECMAScript's Language Specification.

njw
  • 51
  • 2
2

It is not wrapping. It is exactly working as the documentation you've linked. In your linked documentation, it says:

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

Your num is 2147483647, and this number in two's complement format is:

01111111 11111111 11111111 11111111

After left shifting num by 1, it becomes:

11111111 11111111 11111111 11111110

The above 32-bit two's complement value is -2 in decimal number. Hence, you get:

> num
2147483647
> num << 1
-2

If you call toString(2) with a negative decimal number -2, it follows some special rule of toString(). What it does is:

  1. Take the magitude of the decimal. (-2 => 2)
  2. Convert the decimal to base-2 string representation. (2 => '10')
  3. Put the minus sign in front. ('10' => '-10')

Therefore, you get:

> (num << 1).toString(2)
> -10

You also get:

> (-2).toString(2)
> -10
nglee
  • 1,913
  • 9
  • 32
1

Today I check your question again then know I misunderstand your main point yesterday.

This is your main point: " I'd expect to have a bitmask of 31 1s followed by 1 0. Instead, I get -10."

Because Two's complement.

A negative number doesn't express value directly.

let num=Math.pow(2,31)-1
//num in memory=> 0111111111111111111111111111111
//                ^ 
//                |
//            signed bit
//This number is positive, so
//nothing to do=> 0111111111111111111111111111111

num = num<<1
//num in memory=> 1111111111111111111111111111110
//                ^ 
//                |
//            signed bit
//This number is negative, so flip then add 1
//flip         => 1000000000000000000000000000001
//add 1        => 1000000000000000000000000000010 => '-10'
njw
  • 51
  • 2