简体   繁体   中英

I need an explanation (and possible workaround) for ((2^32-1) << 0) resulting -1 in Javascript

EDIT: Explanation at the end.

I was trying to implement a 64bit integer class using a Uint32Array and have bitwise operations performed under the hood on two uint32 members. I quickly found out that, as to my understanding of the specification, bitwise operations return a signed 32bit integer. Initially I was hoping that the Uint32Array would just take care of the sign bit, but it doesn't.

I tried coding around the sign issue, but I am stuck at something I simply can't make sense of at all .

var a = (Math.pow(2, 32)-1); //set a to uint32 max value

So far, so good.

a.toString(2);// gives "11111111111111111111111111111111", as expected

However:

(a << 0);             // gives "-1"
(a >> 1);             // gives "-1"
(a << 0) == (a >> 1); // evaluates to true

Even if JS bitwise operations turn numbers into signed 32bit integers, 32 set bits shifted to the right by 1 should never be -1. Or should they? Should a non-zero number shifted by 0 bits equal itself shifted 1 bit? Is this a bug? Am I running into undefined behaviour?

Usually the answer to similar questions has to do with the signed 32bit conversion but I can't see how that should cause this behaviour.

EDIT2, explanation: The cause of my confusion was a fundamental misunderstanding of how negative numbers are represented in binary. While the first bit is in fact the sign bit, 1 indicating a negative, 0 a positive number, the remaining bits aren't just used to store the abs(), as I assumed.

Signed 4bit example: 0111 equals +7 . 1111 does not equal -7 , it equals -1 . How do we end up with negative one? Because the two's complement of 1111 is 0001 . To get a number's two's complement, flip all bits and add one: 1111 -> 0000 -> 0001 .

Now that I know that, making sense of 11..11 << 0 being -1 is easy. It's perfectly similar to my 4bit example. 11..11 >> 1 being -1 is also completely expected now. The signed right shift >> is 1 filling, so 11..11 >> 1 is still 11..11 which is still -1 .

I will leave this as is for now, because I'm certainly not the only one misunderstanding binary signed integer representation. Thanks for everyone's time.

Even if JS bitwise operations turn numbers into signed 32bit integers, 32 set bits shifted to the right by 1 should never be -1. Or should they? Should a non-zero number shifted by 0 bits equal itself shifted 1 bit? Is this a bug? Am I running into undefined behaviour?

That's normal, expected and defined. And yes, they should.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Right_shift is what you use, and its description is this:

The right shift operator (>>) shifts the first operand the specified number of bits to the right. Excess bits shifted off to the right are discarded. Copies of the leftmost bit are shifted in from the left. Since the new leftmost bit has the same value as the previous leftmost bit, the sign bit (the leftmost bit) does not change. Hence the name "sign-propagating".

So if you have 32 bits of 1, after applying right shift by 1 you will have 32 bits of 1.

The fact that it's 32 bits wide is in the specs, https://tc39.es/ecma262/

6.1.6.1.10 Number::signedRightShift ( x, y )
[...]
4. Return the result of performing a sign-extending right shift of lnum by shiftCount bits. The most significant bit is propagated. The result is a signed 32-bit integer.

(Similarly, << produces 32-bit signed integer)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM