1/0 is infinity but 1/(-0) is -infinity
The same goes for the floating-point infinity: it doesn't represent the concept of infinity, it is a placeholder for every number between the largest representable number and infinity. That's how dividing a very large number by a very small number can result in infinity, a number which is really not a number.
This is the philosophy by which IEEE floating-point numbers were designed, and it's the explanation behind which negative zeroes and infinities make sense in floating point.
The way I find it easiest to reason about is by taking a graph of an asymptote and rounding it to the nearest units. You somehow need a way to say "there is an asymptote here!" even though you might not have all the units, and so you introduce infinities and negative zeroes to maintain as much precision as possible.
Totally NOT. NaN is defined as 0.0/0.0. That's it.
For instance:
- as parent said 1/0 is Inf.
- as parent said 1/(-0) and log(0) is -Inf.
- sqrt(-1) and 0/0 is NaN.
[1] https://www.gnu.org/software/libc/manual/html_node/Infinity-...
what we have is lim(x->0+) 1/x = +inf and lim(x->0-) 1/x = -inf
however, it is irrelevant to the fact that a commenter was disagreeing because they had confused ieee-754 division with the division operation on so-called 'real' numbers
"There are mathematical structures in which [dividing by zero] is defined. [...] However, such structures do not satisfy every ordinary rule of arithmetic (the field axioms)." https://wikipedia.org/wiki/Division_by_zero
You're familiar with the ordinary rules of arithmetic (ORA!) applied to the real numbers. However, there are many number systems that follow different rules.
The rules of arithmetic for floats, wraparound ints, elliptic curves, and asymptotic analysis are all different from ORA! (and each other) but they are useful in their own ways.
That's not at all true. The inclusion of negative zero in ieee 754 was a deliberate design choice, and if it were considered desirable that zero should be unsigned, then the bit pattern assigned to -0 would not have been so assigned. See 'much ado about nothing's sign bit'[0], by one of the principal authors of ieee 754.
Whereas -0 and +0 are not operationally equivalent, operational equivalence is an important and useful relation; see the very pattern matching example linked from the OP[1].
=:= (=== in elixir) Somewhat means "and has the same binary form"
Fwiw, julia made the exact same choices.
This is actually a valid distinction because these values can represent different values.
Floating point values are not numbers, but ranges of possible values: float -0 is a number in the range (-FLT_TRUE_MIN / 2, 0] and float +0 is a number in the range [0, FLT_TRUE_MIN).
IMO if I were to redesign IEEE 754 I would add a third float 0 value that always represents exactly 0 and redefine float -0 and float +0 as open limits, but that would probably make everything a lot more complicated.
+0.0 == -0.0 is true, I guess because they are very close
+0.0 =:= -0.0 is false, I guess because they have different bits
A =:= B for two different numbers of course, because they have different bits
How does == work for other very close floating point values in Erlang? Is there some inconsistency here?
-0.0 is a weird dumb non mathematical value that IEEE 754 put in (for example the standardized value for sqrt(-0.0) is not even justifiable based on their choices). I think they didn't want 1/(1/-Inf) to be Inf, or something.
In the end think of erlang's "==" as "equal in value" and "=:=" as "equal in representation in memory".
Now if you really want to raise the hackles of someone using Erlang, should ask why integers and floats don't have a well ordering, and what you should do when you sort them.
While =:= is not something mandated by IEEE. Erlang implements such an operator on floats and it decides to compare false for 0.0 & -0.0. So it probably compares bits/does integer comparisons on same data
HN is full of people who think the world needs their hot take based on a headline, unfortunately.
What for though?
I was expecting as I learned Rust that I would soon find the == operator (which is just the predicate PartialEq::eq) wasn't quite enough and need some === type operator (Rust doesn't have one), but I never did. Turns out actually in most cases equality is what I cared about.
however, that operator was originally defined incorrectly, and now they are fixing that bug
i don't know what it would mean to not unify cases where the ieee floating point number had the same representation, assuming that by 'unify' you mean 'paattern-match) (erlang doesn't have full bidirectional unification where run-time data can contain variables which could be instantiated with constants from a pattern). what would the semantics of unification be then? no pattern could ever match a term containing a floating point number?
or perhaps you aren't aware that 'unify' means 'pattern-match' (prolog style) and we are discussing a pattern-matching bug, and by 'unify cases' you just mean 'remove one of two cases'. i think it's very likely that the compiler needs to remove redundant cases that fall out of its optimizer in order to get decent performance, and if the user puts them in their code it's probably a bug
No equivalence matching on floating point numbers seems like a much healthier approach, yes. I don't expect people to have huge problems with "x greater than 0.0" or "z less than 16.35" but "p is exactly 8.9" suggests muddled thinking.
The floats are very strange. While machine integers are already a bit weird compared to the natural numbers we learned in school, the floats are much stranger so I expect code which is trying to test equivalence on these values is going to keep running into trouble.
We know that humans, including human programmers, do not appreciate how weird floats really are, so I think telling programmers they just can't perform these matches will have a better outcome than confusing them by allowing this and then it rarely has totally unexpected consequences because a+epsilon was equivalent to b even though mathematically a+epsilon != b
you have to get rid of order comparisons too, though, or people will just replace a == b with a >= b && a <= b
also it seems like a compiler attempting to determine whether two pieces of code are equivalent (so it can throw one away) needs to be able to test whether two constants in them could ever produce different computational results; this is important for code-movement optimizations and for reunifying the profligate results of c++ template expansion
similarly, a dataflow framework like observablehq needs to be able to tell if an observable-variable update needs to propagate (because it could change downstream results) or not; for that purpose it needs to even be able to distinguish different nans. like the compiler, it needs the exact-bitwise-equality relation rather than ordinary arithmetic equality
where i think we agree is that floating-point arithmetic is probably a bad default for most people most of the time because it brings in all kinds of complexity most programmers don't even suspect
your comment reads to me as 'i don't understand floating point equality and therefore no human does so no human should have access to it'
but there are more things in heaven and earth than are dreamed of in your philosophy, tialaramex
"Exact-bitwise-equality" is yet a further different thing from equivalence. Is this what Erlang's =:= operator does? Erlang's documentation described it as "Exactly equal to" which is a very silly description (implying maybe == is approximately equal to), presumably there's formal documentation somewhere which explains what they actually meant but I didn't find it.
Presumably Exact-bitwise-equality is always defined in Erlang? In a language like Rust or C++ that's Undefined in lots of cases so this would be a terrible idea. I still think it's better not to prod this particular dragon, but you do you.
Zero is already special cased in IEEE 754 (along with the rest of the denormals, because of the implied leading mantissa bit), the implementation would be no harder if you just only had a single zero. However, back when the standard was being created, when a single zero value was proposed there was pushback from people in the user community who depended on knowing which side of the zero they underflowed on their own floating point implementations.
Many people think that negative zero is just an engineering artefact. This is not true, it is a feature that was asked for, debated, and then added...
... almost certainly because it was an engineering artefact in a previous system that someone managed to depend on, https://xkcd.com/1172/ style.
Allowing zero to be represented with two bit patterns was a feature that was advocated for to make bit operations easier - it was not decided that both zeroes should be distinguishable. This absolutely is an engineering artifact but you're quite correct that it was quite intentional.
In floating point terms infinity tends to indicate overflow (any number that is too big to represent) and the 0's indicate underflows. So in more verbose terms,
1/(positive overflow) = (positive underflow)
While
-1/(positive overflow) = (negative underflow)
In this case, since the positive overflow isn't really infinity and the underflows aren't really 0, they are not equal. In practice, -0 and the 0 can both also arise from situations that produce 0 exactly, too, but this is not that case.
You may be thinking about how lim{x->inf}(1/x) = 0 = lim{x->inf}(-1/x), which is true. Infinity in floating point does not necessarily represent that limit, though, just any number that is too big.
You may also notice that the limit is not in the range of the functions inside the limit. For all real x, 1/x != 0
And maybe complex number shenanigans and multi valued functions? Maybe someone familiar with mathematics can tell us more. :)
In the case of +0.0 and -0.0, they can be treated as values between zero and the smallest representable number (about 5.4E-079 for 32 bit floats). It isn't a very common use case since dealing with such small numbers isn't a very common thing, but it is definitely a possibility.
if you want to say "zero approaching from negative one", fine, say that. but that IS NOT the same thing as -0. zero is just zero, it doesn't have a sign.
some people just want everything to fit in a nice neat box, and sometimes folks, life is not like that. this is an awful change, injecting meaning where there is no room for it.
Compare/contrast: the JSON document "0" vs the JSON document "0.0". Both encode the same type (JSON only has one Number type) and so the same mathematical object — which is, again, 0 — but these are distinct JSON documents.
And that's the whole point of this change. Erlang == is mathematical equality. Erlang =:= is (encoded bit-pattern) representational equality.
Which is often the kind of equality you care about, if you have canonicalization happening at parse time, and so you want to implicitly assert (usually for added efficiency!) that the values you got after parsing + mathing are not only mathematically equal to some value, but representationally equal to that value.
---
(If this irks you as a mathematician, you could model IEEE754 as its own mathematical group — a lattice, I think? — of which +0 and -0 would be members. Along with with some — but not all — base-2 rationals; and a random assortment of signalling and non-signalling NaNs.)
nope. both those documents are a STRING type, not a number type.
A JSON document is itself canonically a string / stream of human-writable bytes. (I.e. the JSON specification specifies JSON documents canonically as a string-encoding grammar, not as a set of formal AST-node types like SGML/XML/etc.) When you mention a JSON document in text, you're mentioning an encoded string, so you put it in quotes.
Backticks to represent what you'd type at a REPL: if `"foo"` is a JSON document, then `foo` is the decoding of that JSON document (which will not itself be a JSON document, but rather a primitive or data structure in your programming language — one which happens to have a valid encoding as a JSON document.)
0
and this JSON document: 0.0
might compose a different stream of bytes, but by your own admission, they get parsed as the same type and value, 0. the only way to distinguish the difference in a parsed document, would be to present a string type as the input document: ["0", "0.0"]
but even then, you're only retaining difference in meaning after parsing, BECAUSE the values are strings, not numbers. so your comments have only proven that some byte arrays are different, and some resultant strings are different than others. so congrats?In Python the first becomes an int, the second a float.
Er... yes? What I'm trying to do here, is to provide an intuition for how IEEE754 bit patterns should be mentally modeled. They're byte-arrays that encode a semantic type, like a JSON document is a byte-array that encodes a semantic type.
And, just like `"0"` and `"0.0"` are different encodings of the same JSON-semantic Number, IEEE754 -0 and IEEE754 +0 are different encodings of the same mathematically-semantic number; namely, zero.
But, unlike with JSON — and I hope this should be obvious — there's no such thing as "decoding" IEEE754 to something else. The bit-patterns in IEEE754 just are... themselves. There's no more-canonical encoding to move them into that will erase the difference between IEEE754 -0 and +0, or between regular numbers and denormals, or between all the different NaNs. They're all distinct IEEE754 objects.
Which means that, unlike with JSON Numbers, where you just decode them to canonicalize them and can forget about the fact that there are multiple equally-canonical JSON encodings for any given decoded value... you can't just forget about there being multiple distinct IEEE754 objects with the same mathematical meaning.
You don't operate on JSON documents in their encoded form. But you do operate on IEEE754 bit-patterns in their encoded form. You don't turn them into "numbers" in some abstract sense, do math, and then turn them back into bit-patterns. They're bit patterns the whole time.
Every time the FPU does an IEEE754-math operation, it has to know what to do with those distinct equally-canonical encodings, applying logic to them such that they produce mathematically-semantically-correct — but potentially still bit-pattern distinct! — results. Your FPU isn't attempting to simulate mathematics over the field of the rationals (in the way that an arbitrary-precision decimal library would); it's doing specific IEEE754 operations that move an IEEE754-bit-pattern-typed register from containing a value representing one position on the IEEE754 lattice, to a value representing a different position on the IEEE754 lattice. Where some of those position-values of the lattice may be interpreted, by humans, to represent a mathematical number. (Non-uniquely, because -0 and denormals!)
There is no other domain to move the math into. You're stuck in encoded non-canonical bit-pattern land. And as such, it's helpful to be able to talk about the particular encoded non-canonical bit-patterns themselves, compare them for bit-pattern equality, and so forth. Because what those bit-patterns are can determine things like how long an operation takes, or how well a vector of said bit-patterns compresses, or whether external systems will be able to decode the results, or whether you'll still have any data at if you try to hard-cast your FP32 to an FP16.
(And don't get me started on the fact that computer "integers" aren't mathematical integers! At least IEEE754 is a standard, such that `float` means "position on the IEEE754 FP32 lattice" on every architecture targeted by programming languages with FP types, and when an arch doesn't have that support, it's shimmed in with a runtime IEEE754-correct FPU support library. The C machine-integer types — not just `int`, all of them — aren't even well-defined as being a position on a two's-complement ring vs a position on a signed-magnitude ring! Any program that ANDs two machine-integers together and expects a particular bit-pattern to result, is completely non-portable to many historical and esoteric architectures! Which comes up more than you'd think, because mathematical proof-assistants are effectively "esoteric architectures.")
Huh? You can totally take the raw bytes in an IEEE754 floating point number and parse them into something else (say, a decimal type) the same way you can take the raw bytes of a JSON document and parse it into a struct.
It's a lossy encoding for reals.
See also: the problems with stuffing a high magnitude integer like 2^60+7 into a JSON document, and then unpacking it in JS using the standard web browser API. The number can't be represented exactly with the standard JS number type, but it's a valid JSON document, and so the implementation has to make a choice.