Repository navigation
You actually can get Infinity type if you're clever and other fun floating point oddities #31752
Description
Activity
Of course it doesn't quite work out, as neither
InfinityorInfinity as constwill satsifytype Inf. Pretty cool though!dragomirtitian commented
on Jun 4, 2019 ContributorMore actionsNicholai Main (@nmain) Fun trick. Github definitely needs a "please don't put that in your code reaction" 😊
Reacted by Nikhil Mehta, Omri Luzon, Leon Adler, esheyw and Jimmy Zhening LuoReacted by Ricardo Fernández SerrataInfinitywill not satisfyInf🤔
// 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
Reacted by Titian Cernicova-Dragomir, Ashlynne Mitchell, Alistair Smith, Han Seoul-Oh, Anurag Hazra, Fatih Altinok, Twilight, Omri Luzon and 정해준Reacted by Titian Cernicova-Dragomir, Nicholai Main, Soichiro Miki, Ashlynne Mitchell, Alistair Smith, Han Seoul-Oh, Jonah Snider, cobalt, Ricardo Fernández Serrata, Leon Adler and 2 moreReacted by Ricardo Fernández Serrata and Jimmy Zhening LuoReacted by AnyhowStep, Ashlynne Mitchell, Alistair Smith, Anurag Hazra and Ricardo Fernández SerrataReacted by Ricardo Fernández SerrataI 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
Reacted by Han Seoul-Oh, Jonah Snider, cobalt and ExE BossReacted by Ricardo Fernández SerrataReacted by Ricardo Fernández SerrataReacted by Ricardo Fernández Serrata and Omri LuzonReacted by Ricardo Fernández SerrataIEEE
INFhas some really useful properties which aren’t strictly mathematically correct (such asx / 0 = inforx % 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:-0is indeed substitutable for0(as they compare equal). Likewise for the ones with rounding errors.Reacted by ExE BossReacted by Ricardo Fernández SerrataAt 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 | InfinityThis 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. 🙃
Reacted by Jonah Snider, Ricardo Fernández Serrata and Jimmy Zhening LuoOne 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.Reacted by Ricardo Fernández SerrataIEEE 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.
Reacted by Ricardo Fernández Serrata- addedDiscussionIssues which may not have code impactIssues which may not have code impact
on Jun 13, 2019 theverymodelofamodernmajorgeneral commented
on Sep 15, 2022 More actionsEven 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.
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.
Reacted by ExE Boss and Ricardo Fernández SerrataMulti-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,
-0means "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 why1 / 0 == Infinity && 1 / -0 == -Infinityinstead ofNaN. 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 postOne 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
Reacted by Ricardo Fernández SerrataReacted by Ricardo Fernández SerrataInspired by this discussion and by others as well, I tried to implement a
FiniteNumbertype, 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.Amine ER (@amine-err) It's already done; but since you're learning, I encourage you to continue writing your own solutions!
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
Infinityhidden 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
Finitetype using the already sharedinfinityhidden type here, my implementation started an entirely different way, as it is based not on theInfinityhidden type, but on the fact that the valuesInfinityandNaNare not literal number types, and so it creates aLiteralNumbertype that is the basis of my solution and works for bothInfinityandNaNvalues (which the solution you shared doesn't), and I only added theInfinityhidden 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 !
Reacted by Ricardo Fernández Serratait doesn't really add anything
I was confused until I realized you want to exclude
NaNif 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 😅Reacted by Amine ERInspired by this discussion and by others as well, I tried to implement a
FiniteNumbertype, 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 withifProgramIsSimCityThenDontImmediatelyFreeMemory()check.Reacted by Amine ER, Ricardo Fernández Serrata, Matthew Taylor and ExE Boss
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:
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:
Here's another.
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
Expected behavior:
Actual behavior:
Playground Link:
Here
Related Issues: