Skip to content

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

Open

Description

@2A5F

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

Activity

  1. changed the title [-]bigin cant == number[/-] [+]'bigint' is not comparable to 'number' with loose equality[/+] on Mar 22, 2019
  2. calebsander commented on May 23, 2019

    @calebsander
    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.

  3. BTOdell commented on Jun 8, 2019

    @BTOdell

    Caleb Sander Mateos (@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?

  4. calebsander commented on Jun 8, 2019

    @calebsander
    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'".

  5. 2A5F commented on Jun 9, 2019

    @2A5F
    Author

    Caleb Sander Mateos (@calebsander)
    should be error only when strictly

  6. calebsander commented on Jun 9, 2019

    @calebsander
    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.

  7. getify commented on Mar 6, 2020

    @getify

    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 ==.

  8. abacabadabacaba commented on Dec 1, 2020

    @abacabadabacaba

    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.

  9. pfusik commented on Feb 6, 2023

    @pfusik

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

    Please fix it.

  10. kirkwaiblinger commented on Dec 24, 2024

    @kirkwaiblinger

    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.

  11. kevincox commented on Dec 24, 2024

    @kevincox

    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.

  12. kirkwaiblinger commented on Dec 24, 2024

    @kirkwaiblinger

    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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Awaiting More FeedbackThis means we'd like to hear from more people who would be helped by this featureSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions