Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Number("x") == BigInt("x") ... only sometimes?

Tags:

javascript

I thought that JavaScript's loose equality operator was being nice and letting me compare Numbers with BigInts:

42 == 42n  // true! 

So I tried a number bigger than Number.MAX_SAFE_INTEGER. I figured that since the number gets rounded up automatically, I might have to round up the BigInt as well for them to be considered equal:

9999999999999999 == 10000000000000000   // true - rounded to float64  9999999999999999  == 9999999999999999n  // false - makes sense! 10000000000000000 == 9999999999999999n  // false - makes sense! 9999999999999999  == 10000000000000000n // true  - makes sense! 

Great, makes sense — so then I tried another big number that gets rounded up:

18446744073709551616 == 18446744073709552000   // true - rounded to float64  18446744073709551616 == 18446744073709551616n  // true?! 18446744073709552000 == 18446744073709551616n  // true?! 18446744073709551616 == 18446744073709552000n  // false?! 

I observed the same results in Chrome, Safari, and Node.js.

Why isn't this behavior consistent? Is it because the numbers are compared as mathematical values, and what does that mean?

bigint equality table

like image 391
jtbandes Avatar asked Jun 23 '21 23:06

jtbandes


People also ask

How do you know if a number is BigInt?

The easiest way to determine if you're working with a BigInt is to look for the letter n in your code following a numeric value. The letter n means the value is a BigInt ( 37n ), not a number ( 37 ).

How do I convert my number to BigInt?

Syntax. To differentiate a BigInt from a Number you have to append the letter n to the end of the number. For example, 156 is a Number, 156n is a BigInt.

Can you explain BigInt and symbol?

BigInt is a special numeric type that provides support for integers of arbitrary length. A bigint is created by appending n to the end of an integer literal or by calling the function BigInt that creates bigints from strings, numbers etc.

What is considered a BigInt?

BigInt is a new data type intended for use when integer values are larger than the range supported by the Number data type. This data type allows us to safely perform arithmetic operations on large integers, represent high-resolution timestamps, use large integer IDs, and more without the need to use a library.


2 Answers

The inconsistency is because the Number::toString abstract operation is underspecified.

Your question boils down to BigInt(String(x))BigInt(x), which might assumed to be an equality for integer numbers x but is in fact not.

In your particular case, for x=18446744073709551616 or x=18446744073709552000 (or anything in between, and even a bit around), the string representation of the number yields '18446744073709552000' whereas the exact mathematical value is 18446744073709551616. (We know this because the NumberToBigInt operation is exact - it gets you the mathematical value of the number, or an error if it's not an integer).

We also find the following note on Number.prototype.toFixed:

The output of toFixed may be more precise than toString for some values because toString only prints enough significant digits to distinguish the number from adjacent Number values. For example,

(1000000000000000128).toString() returns "1000000000000000100", while
(1000000000000000128).toFixed(0) returns "1000000000000000128".


To answer the titular question

Why does Number(“x”) == BigInt(“x”) … only sometimes?

It's because the limited precision of floating point Number values. There are multiple numeric literals that are parsed to exactly the same number. Similar to your first example, let's take the bigint 20000000000000000n. There is a floating point number with the same mathematical value, specifically

   +1 × 0b10001110000110111100100110111111000001 × 20b10001
= 1 × 152587890625 × 217
= 20000000000000000

There are multiple integer number literals that evaluate to this Number value: 19999999999999998, 19999999999999999, 20000000000000000, 20000000000000001, and 20000000000000002. (Notice it's not always rounding up). This is also what happens when you take these as strings and use unary + or Number on them.

But if you take the respective bigint literals with the same textual representation, they will evaluate to 5 different BigInt values. Only one of which will compare equal (with ==) to, i.e. have the same mathematical value as, the Number value.

This is consistent: there's always exactly one, and it's the one that can be represented precisely as a floating point number.

Why isn't this behavior consistent?

Your confusion comes from the string representation of the Number value 18446744073709551616. When printing the number literal 18446744073709551616, or also +'18446744073709551616', you got 18446744073709552000 (because the console uses String()/.toString() internally), which made you assume that 18446744073709552000 was its mathematical value, and that 18446744073709552000n should compare equal to it. But it's not :-/

console.log(18446744073709551616..toString()); console.log(18446744073709551616..toFixed()); console.log(18446744073709552000..toString()); console.log(18446744073709552000..toFixed());

Which to believe?

like image 134
Bergi Avatar answered Sep 23 '22 19:09

Bergi


My guess is that this is due to the exact implementation of mathematical value not being specified (or at least I couldn't find it).

The abstract equality comparison is specified https://262.ecma-international.org/11.0/#sec-abstract-equality-comparison

If Type(x) is BigInt and Type(y) is Number, or if Type(x) is Number and Type(y) is BigInt, then

If x or y are any of NaN, +∞, or -∞, return false.

If the mathematical value of x is equal to the mathematical value of y, return true; otherwise return false.

So a BigInt should have a mathematical value of some integer, and a Number should have a mathematical value of some kind of scientific notation number, and so the comparisons should be intuitive, but exactly how your JS engine implements mathematical value is not following the spec in spirit.

like image 44
qwr Avatar answered Sep 21 '22 19:09

qwr