Skip to content

You actually can get Infinity type if you're clever and other fun floating point oddities #31752

Description

@rickwebiii

As explained in #15135 and #15351, the Typescript team doesn't intend to support NaN and Infinity literals, due to complexity. Explicitly typing something as Infinity or -Infinity gives the error:

'Infinity' refers to a value, but is being used as a type here.

As it turns out, you actually can get the Infinity type fairly easily. Just use a really large number that parses to infinity as your type, like 1e999. -1e999 gives you negative Infinity. Fortunately, I don't believe you can get NaN from just parsing a double precision literal other than NaN itself. 0/0 is the simplest way to get NaN, but that's 2 literals and a divide.

There are a number of other floating point literal peculiarities resulting from round-off as well. I fully expect the Typescript team to "won't fix" them:

type DoesntExtend = 1.000000000000001 extends 1 ? 'yes' : 'no' // Resolves to 'no'
type Extends      = 1.0000000000000001 extends 1 ? 'yes' : 'no' // Resolves to 'yes'

Here's another.

type NegativeZero = -0 extends 0 ? 'yes' : 'no'; // Resolves to 'yes'

In this case, -0 and 0 are actually distinct numbers with different bit patterns. Both Chrome and Firefox dev consoles correctly echo -0 as -0 rather than 0. However, the IEEE-754 standard defines that 0 === -0, so I'm not surprised Typescript thinks they're the same type. The intent for this distinction is to convey the idea of limits as a numeric representation (not to mention floating point is sign-magnitude anyways, so why not embrace it). For example, 1/-Infinity and -8*0 both result in -0 rather than 0. For a type system, I'm not sure that you strictly need or want to maintain this distinction.

TypeScript Version: Repro'd on playground

Search Terms:
Infinity, NaN
Code

type Illegal = Infinity; // Doesn't compile
type Inf = 1e999; // Resolves to Infinity
type NegativeInf = -1e999; // Resolves to -Infinity

Expected behavior:

Actual behavior:

Playground Link:
Here

Related Issues:

Activity

  1. nmain commented on Jun 4, 2019

    @nmain

    Of course it doesn't quite work out, as neither Infinity or Infinity as const will satsify type Inf. Pretty cool though!

  2. dragomirtitian commented on Jun 4, 2019

    @dragomirtitian
    Contributor

    Nicholai Main (@nmain) Fun trick. Github definitely needs a "please don't put that in your code reaction" 😊

  3. jcalz commented on Jun 4, 2019

    @jcalz
    Contributor

    Infinity will not satisfy Inf

    🤔

    // THIS CODE IS EVIL AND YOU SHOULD RUN AWAY FROM IT
    type Infinity = 1e999;
    if (!((x: any): x is Infinity => true)(Infinity)) throw new Error();
    
    const a: Infinity = Infinity; // okay now
  4. AnyhowStep commented on Jun 5, 2019

    @AnyhowStep
    Contributor

    I don't care what the warnings are. I want those NaN and Infinity literals. I also want a "finite number" type.

    I'm going to find some way to shoehorn that Infinity trick into my code and see if anything explodes

  5. fatcerberus commented on Jun 5, 2019

    @fatcerberus

    IEEE INF has some really useful properties which aren’t strictly mathematically correct (such as x / 0 = inf or x % inf = x) that I’ve actually found it useful in practice from time to time.

    That said—not useful enough to inject that much evilness into my code. 😄

    type NegativeZero = -0 extends 0 ? 'yes' : 'no'; // Resolves to 'yes'

    This one is kind of interesting, because technically by the meaning of extends, the conditional type is correct anyway: -0 is indeed substitutable for 0 (as they compare equal). Likewise for the ones with rounding errors.

  6. rickwebiii commented on Jun 5, 2019

    @rickwebiii
    Author

    At the end of the day, floats are goofy and it might be fine to let the type system reflect that except in a few edge cases you really want to manage. There are an infinite number of decimal representations (memory granted) of every non-NaN number, and I don't think you can rightly ordain one as provenance. This means roundoff on types is just going to be a thing.

    +- 0 is usually not a useful distinction. Most of the cases I can think about are pedantry. For example, Math.sign returns -0 if you pass -0, so you could type its return value as -1 | -0 | NaN | 0 | 1. Without the -0 type, you might be surprised that at runtime 1 / Math.sign(x) can return -Infinity while the return type led you to believe you had to only check NaN, Infinity, +-1. But this is also moot without a NaN literal type.

    I don't know that much about type theory, but I think of the number type as being a very large finite union of one representation of every non-NaN floating point binary sequence (including distinct +0 and -0 and +Infinity and -Infinity) and a single NaN. NaN has many binary representations, but you can't tell them apart in Javascript. This is regardless of what the equality operator does. This looks like:

    NaN | -Infinity | FLOAT_MIN | FLOAT_MIN + ULP | FLOAT_MIN + 2 * ULP | ... -ULP | -0 | 0 | ULP | ... | FLOAT_MAX - 2 * ULP | FLOAT_MAX - ULP | FLOAT_MAX | Infinity
    

    This is how I mentally reason about floating point numbers and having a type system that matches that would be convenient for reasoning about Typescript numbers. However, 1:1 matching the floating point number line to type literals my not be desirable in a type system for reasons I don't yet understand as I'm not a compiler expert and it probably isn't worth the effort to accomplish this as has been stated in other threads.

    At the end of the day, I don't know if I've ever even used number literal types other than in a few toys to show you can; I find strings and enums to be more semantically meaningful for representing finite sets of things, so I use those literal types almost exclusively. With numbers, I almost always want to add, subtract, and multiply arbitrary values at which point you're going to widen any literal types you have anyways.

    That being said, if Typescript is choosing to disallow NaN and Infinity types, then I think it should disallow anything that parses to +-Infinity as well as the literal word Infinity. 🙃

  7. rickwebiii commented on Jun 5, 2019

    @rickwebiii
    Author

    One of my favorite anti-tautologies in Javascript is that typeof(NaN) === 'number'; that which is in fact not a number is in fact number. Forget the usually silliness people point out like [] == false. You just don't get great existential conundrums in languages that make developers choose the signedness, size, and fixed or floatiness of their numbers.

  8. fatcerberus commented on Jun 5, 2019

    @fatcerberus

    IEEE floats actually aren’t that goofy (outside of signed zero and NaN which are IMO both abominations) once you realize it’s just scientific notation in base 2 instead of 10. You have a finite number of bits to represent the mantissa, so it’s only natural that things like repeating decimals and irrationals can’t be represented in that system. That’s also where the “floating” in floating point comes from - increasing the exponent moves the decimal point within a fixed-size mantissa, so you gain insane amounts of range at the expense of precision on the high-end. It’s kind of fascinating—to me anyway.

  9. theverymodelofamodernmajorgeneral commented on Sep 15, 2022

    @theverymodelofamodernmajorgeneral

    Even the NaN and signed zero serve valid purposes. I actually wrote a technical article on why we have NaN in our floating-point standard, if you are interested. The TL;DR is that if you think it's an abomination, you haven't seen the things it's replacing.

    https://www.lucidchart.com/techblog/2022/03/04/if-its-not-a-number-what-is-it-demystifying-nan-for-the-working-programmer/

    As for signed zeros, those are apparently used for contour integration in complex analysis. While I personally know what those words actually mean, I've never needed to actually implement a program that needs the distinction.

  10. Rudxain commented on Nov 10, 2022

    @Rudxain

    Multi-reply

    Quoting Bruce Pascoe (@fatcerberus)

    signed zero [...] abomination

    Quoting theverymodelofamodernmajorgeneral

    Even [...] signed zero serve valid purpose.

    [...] those are apparently used for contour integration in complex analysis

    Actually, -0 means "any negative Real whose order-of-magnitude is too low to be representable as a fixed-precision Float", which is very useful in the context of calculus, because of limits. Limits are the easiest part of calculus, and they are implicitly used outside of calculus as well. This is the reason why 1 / 0 == Infinity && 1 / -0 == -Infinity instead of NaN. See this video by Jan Misali for more info.

    However, to be fair, Object.is(Math.sqrt(-0), -0) && Object.is((-0) ** +0.5, +0) is extremely weird and non-sensical. See related Reddit post

  11. raegen commented on Feb 1, 2025

    @raegen

    One of my favorite anti-tautologies in Javascript is that typeof(NaN) === 'number'; that which is in fact not a number is in fact number. Forget the usually silliness people point out like [] == false. You just don't get great existential conundrums in languages that make developers choose the signedness, size, and fixed or floatiness of their numbers.

    Well, if you actually think about it, if it was an actual number, there would be neither reason nor sense to name it "Not a number", would there? The whole idea behind NaN is that it is a number that is not a number. :D

  12. amine-err commented on Dec 25, 2025

    @amine-err

    Inspired by this discussion and by others as well, I tried to implement a FiniteNumber type, and I think I found something that seems to work for all my use cases (maybe I'm wrong tho), here is how I did it, for anyone interested.

  13. Rudxain commented on Dec 26, 2025

    @Rudxain

    Amine ER (@amine-err) It's already done; but since you're learning, I encourage you to continue writing your own solutions!

  14. amine-err commented on Dec 26, 2025

    @amine-err

    @amine-err It's already done;

    I didn't know about this implementation that you shared, and even if I did, it doesn't really add anything, the one thing that is in common between what you shared and what I did, is the Infinity hidden type, which both, that solution and mine got from the first comment of this discussion, so literally, even if I knew about that solution it wouldn't help me to achieve anything more than reading the first comment here.

    I encourage you to continue writing your own solutions!

    Again I'm still learning, and I believe that reinventing the wheel is a good way to learn, and sharing the way that you implemented a solution to something, even if another implementation of the same solution already exists, isn't bad in of itself. So even if I reinvented the wheel in this case, I wouldn't mind it one bit, because I would've learned a lot while doing it.

    Now the issue here, is that, if you just tried to read what I shared, you would've understood that my implementation is entirely different and adds more to the already existing one that you shared (that again I didn't know about), because as stated before that solution only creates a Finite type using the already shared infinity hidden type here, my implementation started an entirely different way, as it is based not on the Infinity hidden type, but on the fact that the values Infinity and NaN are not literal number types, and so it creates a LiteralNumber type that is the basis of my solution and works for both Infinity and NaN values (which the solution you shared doesn't), and I only added the Infinity hidden type to address one limitation of that. I won't go into details because I already explain this in my page. I also added this section comparing the two solutions if you are interested.

    Anyway, I'm not claiming to be smart, or to have discovered something never seen before, I'm just sharing what I found in the hope that it may help others or just interest them. There are billions of humans out there, and for sure some of them did already discover the same thing, I just shared it here, because I saw some wanting the same thing, and I also searched a lot for something that answers all my needs and didn't find one, or maybe I'm just bad at searching, who knows !

  15. Rudxain commented on Dec 26, 2025

    @Rudxain

    it doesn't really add anything

    I was confused until I realized you want to exclude NaN

    if you just tried to read what I shared

    I did read it before posting the link! But I was so focused on the implementation that I forgot it was meant to exclude NaN. It was an oversight of mine 😅

  16. rickwebiii commented on Dec 28, 2025

    @rickwebiii
    Author

    Inspired by this discussion and by others as well, I tried to implement a FiniteNumber type, and I think I found something that seems to work for all my use cases (maybe I'm wrong tho), here is how I did it, for anyone interested.

    Using a bug as a feature, tsk tsk.
    So this is how Windows 95 wound up with ifProgramIsSimCityThenDontImmediatelyFreeMemory() check.

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

    DiscussionIssues which may not have code impact

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions