5 ms·
NaN == NaN is truly a perversion of equality. It makes little sense that 1/0 is SIGFPE, but log(-5) is NaN in C. And the same is true for higher level languag
by paulddraper 7mo ago
NaN == NaN is truly a perversion of equality.
It makes little sense that 1/0 is SIGFPE, but log(-5) is NaN in C.
And the same is true for higher level languages, and their error facilities.
What a mess.
- Aardwolf 7mo agoPython is extra annoying though with refusing to support division through zero the way other programming languages with IEEE floats do (i.e. output inf or nan instead of throwing an exception), even though it has no problem doing things like float('inf') / float('inf') --> nan. It specifically does it for division through zero as if it wants to be a junior grade calculator just for this one thing. They could at least have fixed this when breaking backwards incompatibility from python2 to 3...
- pletnes 7mo agoJust use numpy if you want to do math. Seriously.
- dgrunwald 7mo agoIn most languages, `x: float = 0` involves an implicit conversion from int to float. In Python, type annotations have no impact on runtime behavior, so even though the type checker accepts this code, `type(x)` will be `int` -- python acts as if `int` was a subtype of `float`. It would be weird if the behavior of `1 / x` was different depending on whether `0` or `0.0` was passed to a `x: float` parameter -- if `int` is a subtype of `float`, then any operation allowed on `float` (e.g. division) should have the same behavior on both types. This means Python had to choose at least one: 1. division violates the liskov substitution principle 2. division by zero involving only integer inputs returns NaN 3. division by zero involving only float inputs throws exception 4. It's a type error to pass an int where a float is expected. They went with option 3, and I think I agree that this is the least harmful/surprising choice. Proper statically typed languages don't have to make this unfortunate tradeoff.
- Aardwolf 7mo agoC does different things for 0.0 / 0.0 and 0 / 0 and it's not that weird to deal with (well it has other issues like it being platform dependent what happens with this). JS has no problem with it either (0.0 / 0.0 gives nan, 0n / 0n gives exception since it are integers). Python is the only language doing this (of the ones I use at least). I don't think the notation `x: float = 0` existed when it was new by the way so that can't be the design reason? since python seems to handle integer through integer divisions as float (e.g. 5 / 2 outputs 2.5), 0 / 0 giving nan would seem to be expected there > liskov substitution principle that would imply one is a subtype of another, is that really the case here? there are floats that can't be represented as an integer (e.g. 0.5) and integers that can't be represented as a double precision float (e.g. 18446744073709551615)
- kbolino 7mo agoPython chose, quite some time ago, not to follow C's lead on division: PEP 238 – Changing the Division Operator (2001) [1] The rationale is basically that newcomers to Python should see the results that they would expect from grade school mathematics, not the results that an experienced programmer would expect from knowing C. While the PEP above doesn't touch on division by zero, it does point toward the objective being a cohesive, layman-friendly numeric system. [1]: https://peps.python.org/pep-0238/ https://peps.python.org/pep-0238/
- xigoi 7mo agoC and JavaScript both treat integers and floats as separate types. In Python, ints and floats with the same numeric value are considered identical for almost all purposes.
- dgrunwald 7mo agoYes `int` acts as if it was a subtype of `float`: https://typing.python.org/en/latest/spec/special-types.html#special-cases-for-float-and-complex https://typing.python.org/en/latest/spec/special-types.html#...
- paulddraper 7mo ago> In Python, type annotations have no impact on runtime behavior That's true of any "scripting" language types. E.g. TypeScript const x: number = 0n; // still is a bigint Granted, in both the Python and the TypeScript example, the type checker will complain at your incorrect type annotation.
- caditinpiscinam 7mo agoNah. Python gets it right; all high level languages should operate this way. Division by zero is a bug 90% of the time. Errors should never pass silently. Special cases aren't special enough to break the rules. IEEE floats should be a base on which more reasonable math semantics are built. Saying that Python should return NaN or inf instead of throwing an error is like saying that Python should return a random value from memory or segfault when reading an out-of-bounds list index.
- paulddraper 7mo agoExactly. Programming language design should be coherent and consistent, not just for floats, but arrays, classes, pointers, or anything else that it offers. And the sensible thing will depend on that language.
- paulddraper 7mo ago> It specifically does it for division through zero as if it wants to be a junior grade calculator just for this one thing. Not just division by zero. math.sqrt(-1) math.log(-2) are errors as well. I agree that using both errors and float('nan') is odd. Tbf, C mixes errors (for integers) and NaN (for floats).
- adamzochowski 7mo agoMakes perfect sense . NaN is a special type indicating one can't reason about it normal way. It is an unknown or value that can't be represented. When comparing, think of it like comparing two bags of unknown amount of apples. One bag has NaN count of apples Other bag has NaN count of apples Do the two bags have equal number of apples? I wish all languages used nulls the way SQL does.
- caditinpiscinam 7mo agoRespectfully, I disagree. If NaNs were meant to represent unknown quantities, then they would return false for all comparisons. But NaN != NaN is true. Assuming that two unknowns are always different is just as incorrect as assuming that they're always the same. I'd also push back on the idea that this behavior makes sense. In my experience it's a consistent source of confusion for anyone learning to program. It's one of the clearest violations of the principle of least astonishment in programming language design. As others have noted, it makes conscientious languages like Rust do all sorts of gymnastics to accommodate. It's a weird edge case, and imo a design mistake. "Special cases aren't special enough to break the rules." Also, I think high level languages should avoid exposing programmers to NaN whenever possible. Python gets this right: 0/0 should be an error, not a NaN.
- glkindlmann 7mo agoAs a standard for floating point representation and computation, IEEE 754 solved multiple long-standing serious problems better than anything that came before it. I don't think its sensible to judge it with a PL design lens like "principle of least astonishment"; certainly not as if IEEE754 is a peer to Rust or Python. Or, you could learn about the surprise, frustration, and expense involved in trying to write portable numeric code in the 1970s, prior to IEEE 754: https://people.eecs.berkeley.edu/~wkahan/ieee754status/754story.html https://people.eecs.berkeley.edu/~wkahan/ieee754status/754st...
- happytoexplain 7mo ago`NaN == NaN` vs `NaN != NaN` forces us to pick which idea to violate: 1. A value of a given type, either a literal or a value in memory, should represent a single concrete value, semantically (`5` represents the number five). 2. Classical logic (if A = B is true, then A != B is false). I vote for violating #1, because we control those terms better. Is it weird that we invented a value that represents "unrepresentable" (as opposed to representing "no value")? Yes, but there are practical reasons for why we did it. It would be much more weird/surprising to violate classical logic.
- nitwit005 7mo agoThere's no non-confusing option for comparisons. You have two invalid values, but they aren't necessarily the same invalid value. There are multiple operations that can produce NaN. It's a sentinel value for an error. Once you have an error, doing math with the error code isn't sensible.
- caditinpiscinam 7mo agoThere are no non-confusing options, but some of those are still clearly worse than others. What should sorted([3, nan, 2, 4, 1]) give you in Python? A) [1, 2, 3, 4, nan] is an good option B) [nan, 1, 2, 3, 4] is an good option C) An error is an good option D) [3, nan, 1, 2, 4] is a silly, bad option. It's definitely not what you want, and it's quiet enough to slip by unnoticed. This is what you get when Nan != NaN NaN == NaN is wrong. NaN != NaN is wrong, unintuitive, and breaks the rest of your code. If you want to signal that an operation is invalid, then throw an error. The silently nonsensical semantics of NaN are the worst possible response
- paulddraper 7mo agoYes. And to boot…it’s only floating point numbers In invalid math op with integers produces an error (or undefined behavior). Definitely not some self unequal abomination.