Daniel Baulig, a co-worker at Facebook, told me a little trick related to jDataView function to convert from a uint8 to a int8 in Javascript.
Here's the version I had:
function getInt8() { var b = this.getUint8(); if (b > Math.pow(2, 7) - 1) { return b - Math.pow(2, 8); } return b; } |
Compare it to his version:
< <function getInt8() { return this.getUint8() << 24 >> 24; } |
I was really confused because it seems like it's doing a no-op. Here's the full explanation of why the two versions are working.
How it works?
The following table (borrowed from Wikipedia) shows how various 8 bits values are in represented with bits and how they are interpreted in unsigned and signed (using two-complement rule).
Bits | uint8 | int8 |
---|---|---|
0000 0000 | 0 | 0 |
0000 0001 | 1 | 1 |
0000 0010 | 2 | 2 |
0111 1110 | 126 | 126 |
0111 1111 | 127 | 127 |
1000 0000 | 128 | −128 |
1000 0001 | 129 | −127 |
1000 0010 | 130 | −126 |
1111 1110 | 254 | −2 |
1111 1111 | 255 | −1 |
Javascript doesn't natively have a 8 bit integer type, it only has a 32 bits one. When you put a 8 bit integer into a 32 bits one, Javascript is going to fill the remaining bits on the left with zeros as the following table shows.
Bits | int32 |
---|---|
0000 0000 ... 0000 0000 | 0 |
0000 0000 ... 0000 0001 | 1 |
0000 0000 ... 0000 0010 | 2 |
0000 0000 ... 0111 1110 | 126 |
0000 0000 ... 0111 1111 | 127 |
0000 0000 ... 1000 0000 | 128 |
0000 0000 ... 1000 0001 | 129 |
0000 0000 ... 1000 0010 | 130 |
0000 0000 ... 1111 1110 | 254 |
0000 0000 ... 1111 1111 | 255 |
Unfortunately, this doesn't properly handle negative numbers. Because we use two-complement, we've got to fill all the bits with 1 for negative numbers in order to have the same number in a signed 32 bits representation.
Bits | int32 |
---|---|
0000 0000 ... 0000 0000 | 0 |
0000 0000 ... 0000 0001 | 1 |
0000 0000 ... 0000 0010 | 2 |
0000 0000 ... 0111 1110 | 126 |
0000 0000 ... 0111 1111 | 127 |
1111 1111 ... 1000 0000 | −128 |
1111 1111 ... 1000 0001 | −127 |
1111 1111 ... 1000 0010 | −126 |
1111 1111 ... 1111 1110 | −2 |
1111 1111 ... 1111 1111 | −1 |
So basically, we've got to fill the 24 remaining bits on the left with the same first bit we have: 0 for positive numbers and 1 for negative numbers.
This is when the trick comes into place. In javascript, there's a binary operator: >>
Sign-propagating right shift that moves all the bits to the right and fills the missing bits with the first bit.
So all we have to do is to put our 8 good digits to the far left using <<
and then use the previous trick to fill the bits with the proper ones 🙂
x | x < < 24 | (x << 24) >> 24 |
---|---|---|
0000 0000 ... 0000 0000 | 0000 0000 ... 0000 0000 | 0000 0000 ... 0000 0000 |
0000 0000 ... 0000 0001 | 0000 0001 ... 0000 0000 | 0000 0000 ... 0000 0001 |
0000 0000 ... 0000 0010 | 0000 0010 ... 0000 0000 | 0000 0000 ... 0000 0010 |
0000 0000 ... 0111 1110 | 0111 1110 ... 0000 0000 | 0000 0000 ... 0111 1110 |
0000 0000 ... 0111 1111 | 0111 1111 ... 0000 0000 | 0000 0000 ... 0111 1111 |
0000 0000 ... 1000 0000 | 1000 0000 ... 0000 0000 | 1111 1111 ... 1000 0000 |
0000 0000 ... 1000 0001 | 1000 0001 ... 0000 0000 | 1111 1111 ... 1000 0001 |
0000 0000 ... 1000 0010 | 1000 0010 ... 0000 0000 | 1111 1111 ... 1000 0010 |
0000 0000 ... 1111 1110 | 1111 1110 ... 0000 0000 | 1111 1111 ... 1111 1110 |
0000 0000 ... 1111 1111 | 1111 1111 ... 0000 0000 | 1111 1111 ... 1111 1111 |