Why doesn't the "|" operator work for large values? - javascript

I was looking at this question about how to efficiently check if a value is an integer. One answer recommended using n === (n|0) for some quick rounding via the | operator. On a whim, I decided to test it with Number.MAX_VALUE. Despite the fact that this should be an integer (I think?), the test came back false.
It also came back false for Number.MAX_SAFE_INTEGER, so I decided to test some other large numbers and found the following:
Number.MAX_VALUE | 0 --> 0
Number.MAX_SAFE_INTEGER | 0 --> -1
Number.MAX_SAFE_INTEGER/2 | 0 --> -1
Number.MAX_SAFE_INTEGER/8 | 0 --> -1
1234567890 | 0 --> 1234567890
I'm not really sure what the | operator is doing internally, but it doesn't seem safe to do on MAX_SAFE_INTEGER. Why is that the case?

The | operator in JavaScript converts its argument to a signed 32 bit integer, then does a bitwise or onto it. |0 or's zero, which leaves the value unchanged, so that has become a JavaScript convention for converting the value to a 32 bit integer (the same type as int in C on desktop processors, a fast datatype for processing as well as a convenient round).
I'm not sure if Number.MAX_SAFE_INTEGER is part of the standard; I just tried it in my IE and it came back undefined, but based on your description, it is probably giving you the biggest number that can fit in either 32 bits or a double (floating point): 2^32-1, about 4 billion in the case of the 32 bit and something much bigger for the float. But remember | gives you a /signed/ number, so there's really only 31 bits available. The other bit is used for negative numbers.
To store a negative number in binary (technically speaking, twos complement), you flip all the bits then add one to the result. One is stored 0001, so flip the bits and add one gives 1110+1 == 1111 (of course, longer value for 32 bit but same concept) which is the same representation as the maximum value when unsigned.

While it is true that MAX_SAFE_INTEGER is an integer... it's an integer in double-precision float format, ie. 53 bits long.
Meanwhile, the bitwise operators work on 32-bit integers.
I think you can see where this is going ;)

Related

I do not understand about bitwise operator in Javascript

I am student studying programming.
As far as I know, Javascript saves number as float.
However, bitwise operator in Javascript run as a type of number is integer.
For instance,
> 1 | 2 // -> 3
> 2 << 4 // -> 32
How is it possible?
I find official documentation(mdn web docs), but I can not find the reason.
The bitwise operators in JavaScript convert numbers to 32-bit integers before performing the operation. This means that even though JavaScript saves numbers as floats, the bitwise operators treat them as 32-bit integers. This is why you are able to use the bitwise operators on numbers and get integer results.
As #rviretural_001 mentioned, JavaScript does some automatic conversions by spec. For example, in addition to IEEE 754 doubles to 32 signed integers:
0 == '0' // true
String to Integer (Shift Right)
'4' >> 1 // 2
Converting to Integer (Bitwise OR)
'1.3' | 0 // 1
This last one was used in asm.js for 32 signed integers
just to contribute to the answers above note that the conversion of a floating-point number to an integer is done by removing the fractional part of the number.
For example, if you have the number 4.5 and you use a bitwise operator on it, JavaScript will convert it to the integer 4 before performing the operation.
This tutorial might help you to find out more https://www.programiz.com/javascript/bitwise-operators
Also note that performing bitwise operations on floating-point numbers can lead to unexpected results. This is due to the way floating-point numbers are represented in memory. So, it's recommended to avoid using bitwise operators on floating-point numbers in JavaScript.

Why my two's complement gives me a completely different result in javascript

I'm currently figuring out a checksum of a byte buffer of 32 bits each byte, I have to calculate two checksums, 32bit unint sum and 32 bit uint xor for every component of the byte buffer (except some locations). The sum works as expected but the xor gives me a weird value.
The value I get from the xor is -58679487 and when applying two's complement I get 58679487 but when converting it to a hex value it is 0x037F60BF and I'm looking for 0xFC809F41. If I place the initial xor value (-58679487) in rapidtables and convert it from dec to hex it displays the correct two's complement value in hex. What am I doing wrong?
i=startAddress;
while(i<buf.length){
if(i !== chk1 && i!== chk2 && i!== chk3 && i!== chk4){
file32Sumt += buf.readUint32BE(i);
file32Xort ^= buf.readUint32BE(i);
i+=4;
}else{
console.log('cks location.'+ buf.readUint32BE(i).toString(16));
i+=4;
}
}
//two's complement
console.log((~file32Sumt+1).toString(16));
console.log((~file32Xort+1).toString(16));
Already did the two's complement by using the bitwise NOT operator (~) then adding 1 but seems it's not working. Also tried using Math.abs(file32Xort) but got the same result.
Don't negate. Use this:
console.log((file32Xort >>> 0).toString(16));
file32Xort has the right value already, but as a signed 32-bit integer. What you need here is the unsigned interpretation of the same value. number >>> 0 is a simple way to do that reinterpretation.
Additionally, I think you should write
file32Sumt = (file32Sumt + buf.readUint32BE(i)) >>> 0;
.. or something to that effect, this time using some bitwise operator (it doesn't have to be an unsigned right shift, but I think that makes sense in this context) to prevent the sum from becoming too large (by limiting it to 32 bits) and potentially exhibiting floating point rounding. The calculation of file32Xort already uses a bitwise operator so it doesn't need an extra one like that, only at the end to reinterpret the result as unsigned.

Why does right shift on positive number sometimes result in a negative number?

Right shifting a number in javascript sometimes results in a negative number. What is the reason behind that? Can that be mitigated?
const now = 1562143596806 // UNIX timestamp in milliseconds
console.log(now >> 8) // -4783199
Use the zero-fill right shift operator (>>>) to always get a positive result:
const now = 1562143596806 // UNIX timestamp in milliseconds
console.log(now >>> 8)
The reason for the >> operator returning the number is caused by the fact that, originally, the number is internally represented as a 64-bit floating point number:
10110101110110111000000111010000100000110
The bit shift operation will first convert the operand to a 32-bit integer. It does this by keeping only the 32 least significant bits, and discarding the rest:
10110111000000111010000100000110
Then it will shift it by the specified number of bits while maintaining the sign, i.e. shifting in 8 1 bits from the left:
11111111101101110000001110100001
Converting back to decimal, this yields:
-4783199
The basic issue is that 1562143596806 is too large to fit in 32 bits. It can be represented as a Number, but when performing bitwise operations, the value is first converted to a 32bit integer and that means the "top bits" are already dropped before shifting - the upper bits of the result are therefore not filled from the original value, they are copies of the sign of that temporary 32bit value (or with >>>, they would be zero, which is not really an improvement). That the result happens to come out negative is just an accident depending on the exact bit pattern of the input, if it had been positive it would still have been the wrong positive value.
Such large values could be safely manipulated as BigInt, but support for that is lacking. Using floating point arithmetic can work, but requires extra care. For example you can divide by 256 and floor the result, but you cannot use the usual |0 to get rid of the fractional part, because even after dividing by 256 the value is too big to fit in 32 bits. Various non-built-in BigInt libraries exist to deal with this sort of thing too.

Why :Math.floor(2e+21) != ~~(2e+21)

I am not an expert in bitwise operators, but i often see a pattern which is used by programmers of 256k demos at competitions. Instead of using Math.floor() function, double bitwise NOT operator is used ~~ ( maybe faster ? ).
Like this:
Math.floor(2.1); // 2
~~2.1 // 2
Search revealed that there are more patterns that used the same way:
2.1 | 0 // 2
2.1 >> 0 // 2
When playing with this in the dev console, i have noticed a behavior that i'm not sure i understand fully.
Math.floor(2e+21); // 2e+21
~~2e+21; // -1119879168
2e+21 | 0; // -1119879168
What is happening under the hood ?
As Felix King pointed out, the numbers are being converted to 32 bit signed integers. 2e9 is less than the maximum positive value of a signed int, so this works:
~~(2e9) //2000000000
But when you go to 2e10, it can't use all the bits, so it just takes the lowest 32 bits and converts that to an int:
~~(2e10) //-1474836480
You can verify this by using another bitwise operator and confirming that it's grabbing the lowest 32 bits:
2e10 & 0xFFFFFFFF // also -1474836480
~~(2e10 & 0xFFFFFFFF) // also -1474836480
Math.floor is built to account for large numbers, so if accuracy over a big range is important then you should use it.
Also of note: The ~~ is doing truncation, which is the same as flooring for positive numbers only. It won't work for negatives:
Math.floor(-2.1) // -3
~~(-2.1) // -2
As stated in the MDN Docs, and here I quote,
The operands of all bitwise operators are converted to signed 32-bit integers in two's complement format.
This means tha when you apply a bitwise operator, for instance ~, to 2.1 it is first converted to an integer, and only then is the operator applied. This effectively achieves the rounding down (floor) effect for positive numbers.
As to why these operators are used, instead of the much nicer to grasp Math.floor, there are two main reasons. For one, these operators may be considerably faster to achieve the same result. Besides performance, some people just want the shortest code possible. All three operators you mentioned achieve the same effect, but ~~ just happens to be the shortest, and arguably the easiest to remember.
Given that the float to integer conversion happens before the bitwise operators are applied, let's see what happens with ~~. I'll represent our target number (2, after the convertion from 2.1) using 8 bits, instead of 32, for shortness.
2: 0000 0010
~2: 1111 1101 (-3)
~~2: 0000 0010
So, you see, we apply an operator to retrieve only the integer part, but we can't apply only one bitwise not, because it would mess up the result. We revert it to the desired value applying the second operator.
Regarding your last example, take into account that the number you're testing with, 2e+21, is a relatively big number. It's a 2 followed by twenty-one zeroes. It simply doesn't fit as a 32-bit integer (the data-type it is being converted to, when you apply the bitwise operators). Just look at the difference between your number and what a 32-bit signed integer can represent.
Max. Integer: 2147483647
2e+21: 2000000000000000000000
How about binary?
Max. Integer: 01111111111111111111111111111111
2e+21: 11011000110101110010011010110111000101110111101010000000000000000000000
Quite big, huh?
What really happens under the hood is that Javascript is truncating your big number to what it can represent in 32 bits.
110110001101011100100110101101110001011 10111101010000000000000000000000
^---- Junk ----^
When we convert our truncated number to decimal, we get back what you're seeing.
Bin: 10111101010000000000000000000000
Dec: -1119879168
Conversely, Math.floor accounts for the big numbers and avoids truncating them, which is one of the possible reasons for it being slower, although accurate.

Difference between ~~ and Math.floor()

As I see in examples, the functionality if ~~ and Math.floor is the same. Both of them round a number downward (Am I think correct?)
Also I should mention that according to this test ~~ is faster than Math.floor: jsperf.com/math-round-vs
So I want to know, is there any difference between ~~ and Math.floor?
Yes, bitwise operators generally don’t play well with negative numbers. f.ex:
~~-6.8 == -6 // doesn’t round down, simply removes the decimals
Math.floor(-6.8) == -7
And you also get 0 instead of NaN, f.ex:
~~'a' == 0
Math.floor('a') == NaN
In addition to David answer:
One of the things that I have noticed about bitwise operations in JavaScript is that it can be convenient for smaller values, but doesn’t always work for larger values. The reason this is the case is that bitwise operators will only work fully for operands which can be fully expressed in a 32-bit signed format. In other words, using bitwise operations will only produce numbers that are in the range of -2147483648 (-231) to 2147483647 (231 – 1). In addition, if one of the operands used is outside of that range, the last 32 bits of the number will be used instead of the specified number.
http://cwestblog.com/2011/07/27/limits-on-bitwise-operators-in-javascript/
This limitation can easily be found when working with Date, assume you are rounding a milliseconds value:
Math.floor(1559125440000.6) // 1559125440000
~~1559125440000.6 // 52311552

Categories