Skip to content

'bigint' is not comparable to 'number' with loose equality #30540

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
2A5F opened this issue Mar 22, 2019 · 11 comments
Open

'bigint' is not comparable to 'number' with loose equality #30540

2A5F opened this issue Mar 22, 2019 · 11 comments
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript

Comments

@2A5F
Copy link

2A5F commented Mar 22, 2019

TypeScript Version: 3.3.3

Search Terms: bigin == number not comparable loose equality

Code

1n == 1

error:

This condition will always return 'false' since the types 'bigint' and 'number' have no overlap.

but in chrome 72.0.3626.121

> 1n == 1
< true

and in MDN https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt
QQ截图20190322171720

And similar issues

let x = [1, 2, 3]
x[1n]

error:

Type '1n' cannot be used as an index type.

in Chrome

>  let x = [1, 2, 3]
   x[1n]
<  2

Playground Link:
http://www.typescriptlang.org/play/index.html#src=1n%20%3D%3D%201
http://www.typescriptlang.org/play/index.html#src=let%20x%20%3D%20%5B1%2C%202%2C%203%5D%0D%0Ax%5B1n%5D

Related Issues:
#30655

@DanielRosenwasser DanielRosenwasser added Suggestion An idea for TypeScript Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature labels Mar 22, 2019
@DanielRosenwasser DanielRosenwasser changed the title bigin cant == number 'bigint' is not comparable to 'number' with loose equality Mar 22, 2019
@calebsander
Copy link
Contributor

I agree that the error message This condition will always return 'false' is not strictly correct. But comparing number and bigint values for equality should definitely cause a type-checking error. It is not obvious how to compare a bigint to a number—should they be coerced to numbers or bigints before doing the comparison? It makes sense to me that the compiler requires you to explicitly coerce one of them.

Number(1n) == 1.2 // false
1n == BigInt(1.2) // RangeError: The number 1.2 cannot be converted to a BigInt because it is not an integer

Number(BigInt(Number.MAX_SAFE_INTEGER) + 2n) == Number.MAX_SAFE_INTEGER + 2 // true
BigInt(Number.MAX_SAFE_INTEGER) + 2n == BigInt(Number.MAX_SAFE_INTEGER + 2) // false

The compiler gives the same warning if you tried to compare strings and numbers:

// This condition will always return 'false' since the types '"1"' and '1' have no overlap.
'1' == 1 // true

The fact that bigints can't be used as indices is intentional. Arrays, strings, TypedArrays, etc. are meant to be indexed with numbers, so the compiler should warn you if you are indexing with a bigint.
Again, the same is true when strings are used in place of bigints:

// Element implicitly has an 'any' type because index expression is not of type 'number'.ts(7015)
['a', 'b', 'c']['1'] // 'b'

We discussed adding bigint index signatures and eventually decided against it.

@BTOdell
Copy link

BTOdell commented Jun 8, 2019

@calebsander
I just ran into the issue of comparing a string and a number: '0' != 0 // false

TS2367: This condition will always return 'true' since the types 'string' and '0' have no overlap.

The double equals operator should be thought of as a function with two any parameters since it doesn't do type equality like the triple equals operator does. Or maybe it's possible to improve the core type definitions by encoding the coercion rules into them?

@calebsander
Copy link
Contributor

I think it's good that the compiler gives an error in this case. In my experience, comparing values of different types is usually a mistake. If not, you can explicitly convert the number to a string or the string to a number. But I agree that the error message is misleading—perhaps for == it should just say "Cannot compare values of type 'string' and type '0'".

@2A5F
Copy link
Author

2A5F commented Jun 9, 2019

@calebsander
should be error only when strictly

@calebsander
Copy link
Contributor

There are lots of things which are technically valid JavaScript, but TypeScript rightly warns about them because they are usually not what the programmer meant to write. For example, you can multiply two strings in JS and they will be coerced to numbers, but TypeScript forces you to explicitly coerce them. I think this is reasonable behavior, and == and === should both require their operands to have the same type.

@getify
Copy link

getify commented Mar 6, 2020

TS may rightly want to error on people using == at all. But... if someone uses == instead of ===, then TS should accurately make its assumptions about what == will do. It appears from this thread (and others) that's not the case. Wanting to throw useful errors is fine, but incorrectly representing how JS works is not.

IOW, the vastly more common perspective (much to my chagrin) is that devs default to using ===, and that's ESPECIALLY true for those who use type systems like TS. So, if someone is going out of their way to use TS, and they choose a ==, and that's allowed by their linter/settings, then the assumption should be they intended to do that, and that they intended to take advantage of the coercive capabilities of ==.

@abacabadabacaba
Copy link

The interesting thing about comparing numbers with bigints in JavaScript is that it is not implemented by coercing both operands to numbers, nor is it implemented by coercing both arguments to bigints. Instead, the exact real values are compared. Here are some examples:

These values are different:

> 10n**50n == 10**50
false

But they become equal when coerced to numbers:

> Number(10n**50n) == Number(10**50)
true

These values can be compared:

> 1n == 1.5
false

But cannot be coerced to bigints:

> BigInt(1n) == BigInt(1.5)
Uncaught:
RangeError: The number 1.5 cannot be converted to a BigInt because it is not an integer

Currently, it is not possible to get this comparison semantics in TypeScript without using type assertions. I agree that == operator shouldn't have the same strict type checking rules as === operator, as the use of the former signals the intent that the operands may be of different types.

@pfusik
Copy link

pfusik commented Feb 6, 2023

This error makes sense for ===, but not for ==. As @abacabadabacaba explained, there is no easy workaround of casting operands to number or bigint.

Please fix it.

@kirkwaiblinger
Copy link

Currently, it is not possible to get this comparison semantics in TypeScript without using type assertions.

I believe (but could be wrong) that the following replicates the == behavior

function compare(n: number, bi: bigint): boolean {
  return Number.isInteger(n) && BigInt(n) === bi;
}

Note that it's not clear that that's such a useful operation, since BigInts' reason for existing is to handle values outside of the safe integer range, and there be dragons there (as noted in #30540 (comment)):

Number.MAX_SAFE_INTEGER + 2 == BigInt(Number.MAX_SAFE_INTEGER) + 2n; // false
Number.MAX_SAFE_INTEGER + 1.5 == BigInt(Number.MAX_SAFE_INTEGER) + 1n; // true

Therefore, it may well in practice be inappropriate to compare using the == behavior; instead, one of the following may be preferable:

function compare(n: number, bi: bigint): boolean {
  return Number.isSafeInteger(n) && BigInt(n) === bi;
}

function compare(n: number, bi: bigint): boolean {
  if (!Number.isSafeInteger(n)) {
    throw new Error("attempted to compare unsafe number with bigint");
  } 
  return BigInt(n) === bi;
}

Providing this info not particularly to weigh in on whether the == should be allowed here in TS, but as a reference for some relevant info on the topic that hasn't been brought up in this thread yet, and may be helpful to others like me who stumble upon this issue.

@kevincox
Copy link

kevincox commented Dec 24, 2024

Note that it's not clear that that's such a useful operation,

I don't think I agree. The problem in this example isn't at all related to BigInt or the comparison, it is just that you are leaving the range of a Number. That is a questionable thing to do whether or not BigInt is involved. By that argument you can say that === should be banned on numbers because Number.MAX_SAFE_INTEGER + 1.5 === Number.MAX_SAFE_INTEGER + 1.

@kirkwaiblinger
Copy link

By that argument you can say that === should be banned on numbers because Number.MAX_SAFE_INTEGER + 1.5 === Number.MAX_SAFE_INTEGER + 1.

This isn't that preposterous a conclusion 🙂! In most programming languages we do avoid exact equality comparison between floats, and use a separate integer type (or multiple) for integer operations.

It's just in JS, as far as I'm aware, where it's canonical to use the mantissa of floats to represent exact integers, and so we make a habit of performing integer-like operations on floats.


Anyways, the important points I was trying to make were simply:

  1. You can replicate the == behavior without using the == operator or a type assertion

  2. But please use that with caution depending on your specific context. Don't blame me if it was a bad idea for your use case 🫶

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

9 participants