4

Will numeric comparison, between a BigInt and a Number, coerce one argument to BigInt, or coerce one argument to Number?

For example, in the following, is 3n coerced to 3, or is 1 coerced to 1n?

console.log(3n > 1) // true

Both operands appear to be converted to a "mathematical value" in 4.k of the IsLessThan abstract operation; a process that is designated ℝ in the specification.

I have no idea what this means, however.

I note that:

console.log(19_999_999_999_999_998 > 19_999_999_999_999_999n) // true

Perhaps the "mathematical value", ℝ, of 19,999,999,999,999,998, is the descriptor for the value 20,000,000,000,000,000, which is the closest possible IEEE754 representation?

I still don't understand if coercion can be said to occur, given that a Number can have a fractional component, and a BigInt cannot.

Ben Aston
  • 53,718
  • 65
  • 205
  • 331
  • 1
    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#operators – Dave Newton May 26 '22 at 13:17
  • 1
    AFAICT, that document does not explicitly contain the answer to this question. – Ben Aston May 26 '22 at 13:18
  • 1
    The list of operators state which operators can be applied to `BigInt`s. If there's an operator in use on a `BigInt` that isn't in that list then it's not being applied to a `BigInt`. That means it's being coerced to a `Number`. – Dave Newton May 26 '22 at 14:19
  • 2
    I think "mathematical value" relates to the actual underlying value used in the operand. Consider `90071992547411296`, it is larger than `Number.MAX_SAFE_INTEGER`, so its number value is `90071992547411300`, but its mathematical value is `90071992547411296`. So when you do something like `90071992547411299n > 90071992547411296` you get `true`. The right operand isn't treated as its number value (`90071992547411300`), but instead, it is kept and compared as its mathematical value (`90071992547411296`) and so we get `true`. This could be wrong though, so don't take it as fact. – Nick Parsons May 26 '22 at 14:20
  • 1
    Very interesting. Sounds plausible. So is this coercion to common type, or is it something else? – Ben Aston May 26 '22 at 14:21
  • 1
    This question and the answers/comments might be able to clear up what "mathematical value" means: [Why does Number("x") == BigInt("x") ... only sometimes?](https://stackoverflow.com/q/68108016) – Nick Parsons May 26 '22 at 14:21
  • 1
    I've just read the answer to the question you link to and the bottom just fell out of my world. Every time you learn more about the answer, the less you know. Darkness. – Ben Aston May 26 '22 at 14:36
  • 2
    @NickParsons No. The mathematical value of the `90071992547411300` number value is 90071992547411300. There simply is no "number value `90071992547411296`" - if you write a number literal with those digits, it becomes the number value `90071992547411300`. This all happens before the `>` operator is evaluated. – Bergi May 26 '22 at 14:56
  • Thanks @Bergi - I'm still a bit confused then though as to why `90071992547411299n > 90071992547411296` would return `true`. If the number literal of `90071992547411296` parses to the number value of `90071992547411300`, then wouldn't it be comparing the following?: "ℝ(`90071992547411299n`) > ℝ(`90071992547411300`)", which as I understand it, would be "90071992547411299 > 90071992547411300", which would be `false`? – Nick Parsons May 28 '22 at 10:19
  • 1
    Per the answer to [the question you linked to](https://stackoverflow.com/questions/68108016/why-does-numberx-bigintx-only-sometimes), `String()` does not always print the most-precise-available mathematical value of the number. `toFixed(0)` rounds at the decimal separator, so for integers it always prints the maximal precision. `toString()` fills up with zeroes if the additional precision is not needed (and if there are less than 21 digits, otherwise it switches to scientific notation). I don't know why `toString()` it behaves this way, but it does. – Ben Aston May 28 '22 at 16:18
  • 1
    So, some integer Numbers, like `90071992547411296`, have differing `toString` and `toFixed` values. ie. there is precision available that is hidden by `toString`. So `String(90071992547411296)` !== `90071992547411296..toFixed(0)`. `90071992547411296..toFixed(0)` is `90071992547411296`, which is ostensibly less than `90071992547411299n`. Hence `90071992547411299n > 90071992547411296` returns `true`. Why `String` and `toString` behave like this, I do not know. – Ben Aston May 28 '22 at 16:23
  • 1
    `Number.prototype.toFixed` appears to return a value that actually is, or is closer to, the "mathematical value", ℝ, referred to in the spec, used in the comparison used by the numeric comparison operators when the numeric operands are mixed (ie. `BigInt` and `Number`). – Ben Aston May 28 '22 at 16:38
  • 1
    @NickParsons Ben has the perfect answer to your question in his comments, I have nothing to add – Bergi May 28 '22 at 20:22
  • Thanks @BenAston for clarifying. I'm still a bit unsure about somthing like: `90071992547411299n > 90071992547411300`. I would think that it's doing "ℝ(`90071992547411299n`) > ℝ(`90071992547411300`)", which is comparing "90071992547411299 > 90071992547411296" (as `90071992547411300..toFixed()` is `"90071992547411296"`, so as I understand it, that is the mathematical value of `90071992547411300`), so `90071992547411299n > 90071992547411300` gives `true`. However, in Bergi's comment above, he said that _"The mathematical value of the `90071992547411300` number value is 90071992547411300"_ – Nick Parsons May 30 '22 at 12:04
  • ... which makes me think that the comparison is "ℝ(`90071992547411299n`) > ℝ(`90071992547411300`)" is not the same as "90071992547411299 > 90071992547411296", but instead would be "90071992547411299 > 90071992547411300" which would then make the comparison `false`? I guess I am confused about how the "mathematical value" of `90071992547411300` can be 90071992547411300 when something like `90071992547411299n > 90071992547411300` gives `true`? – Nick Parsons May 30 '22 at 12:05
  • 1
    I think @Bergi misspoke in that comment, but he is better informed than I. AFAICT `toFixed` is the closest we can come to seeing the mathematical value. `toString` and `String()` cannot be relied upon to give an accurate rendering of the value, due to the way those functions discard precision. – Ben Aston May 30 '22 at 12:38
  • 1
    @NickParsons "*The mathematical value of the `90071992547411300` number value is 90071992547411300*" - oops, I mixed that up; I should've known better than to use the console output to judge the value (and rather use `BigInt(x)` or `x.toFixed()`, as Ben suggests). You're right, there is no Number value `90071992547411300`, such a number literal parses to the Number value `90071992547411296`, whose mathematical value is 90071992547411296. – Bergi May 30 '22 at 13:06

2 Answers2

3

Will numeric comparison between a BigInt and a Number coerce one argument to BigInt, or coerce one argument to Number?

Neither. As you read in the spec, their mathematical values are compared.

Consider 2n < 2.4. If both were converted to BigInt, you'd either get an exception ("cannot convert non-integer to BigInt") or you'd get the result false because 2n is not smaller than 2n. But no, we get the result true because "ℝ(2n) < ℝ(2.4)" is "2 < 2.4" which is true.

Consider 9007199254740993n > 9007199254740992. If both were converted to Number values, you'd get the result false because 9007199254740993 is larger than the Number.MAX_SAFE_INTEGER and loses precision, becoming the number value 9007199254740992 which is not larger than 9007199254740992. But no, we get the result true because "ℝ(9007199254740993n) > ℝ(9007199254740992)" is "9007199254740993 > 9007199254740992" which is true.

Perhaps the "mathematical value", ℝ, is the closest possible IEEE754 representation?

No, that's the Number value for x, also denoted as (x), which converts from a mathematical value (the real numbers) or extended mathematical value (the real numbers plus two infinities) to a concrete (IEEE 754 double-precision floating point) Number value. The mathematical values are abstract, and there are many real numbers that can neither be represented accurately as a BigInt or Number.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • @BenAston If by the formatting `20_000_000_000_000_000` you want to imply an ECMAScript Number value, then no. It's the abstract mathematical integer 20000000000000000. – Bergi May 26 '22 at 15:33
  • ℝ is applied to both operands. `console.log(19_999_999_999_999_998 > 19_999_999_999_999_999n)` prints `'true'`. So whatever ℝ(`19_999_999_999_999_998`) is, it is larger than ℝ(`19_999_999_999_999_999n`). But according to your answer, ℝ(`19_999_999_999_999_998`) does not describe 20,000,000,000,000,000 (that would be ). Note: in `programming-style numbers`, unless I include the suffix of `n`, I mean that value to be an instance of Number. What am I missing? – Ben Aston May 26 '22 at 15:40
  • 1
    @BenAston I mean ℝ(`19_999_999_999_999_998`) is the same as ℝ(`20_000_000_000_000_000`) (because both number literals parse to the same Number value), and both result in the abstract real number 20000000000000000. (My answer should not say otherwise). Then, (20000000000000000) brings that mathematical value back into the ECMAScript Number domain, resulting in `20_000_000_000_000_000`. As does (19999999999999998). – Bergi May 26 '22 at 15:40
  • 1
    @BenAston Forgive me not being clear on my formatting conversions. If I format a number as code, it's an instance of Number; if I format it as code with the `n` suffix, it's an instance of BigInt; if I do not apply formatting it's a mathematical value – Bergi May 26 '22 at 15:43
0

Because coercing between Number values and BigInt values can lead to loss of precision, the following are recommended:

Only use a BigInt value when values greater than 2^53 are reasonably expected. Don't coerce between BigInt values and Number values.

The BigInt is converted to a Number (which can lead to loss of precision).

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt

UPDATE:

For 3n > 1

Bytecode length: 11
Parameter count 6
Register count 2
Frame size 16
OSR nesting level: 0
Bytecode Age: 0
   16 S> 0x28001122e @    0 : 13 00             LdaConstant [0]
         0x280011230 @    2 : c2                Star1 
         0x280011231 @    3 : 0d 01             LdaSmi [1]
   20 E> 0x280011233 @    5 : 6d f9 00          TestGreaterThan r1, [0]
         0x280011236 @    8 : c3                Star0 
         0x280011237 @    9 : 0e                LdaUndefined 
   24 S> 0x280011238 @   10 : a8                Return 
Constant pool (size = 1)
0x2800111c9: [FixedArray] in OldSpace
 - map: 0x0a554d7412c1 <Map>
 - length: 1
           0: 0x0002800111e1 <BigInt 3>
Handler Table (size = 0)
Source Position Table (size = 8)
0x000280011241 <ByteArray[8]>

For TestGreaterThan, it looks like it compares two fixed arrays (BigInt & Int)

maelswarm
  • 1,163
  • 4
  • 18
  • 37