[HN Gopher] In Erlang/OTP 27, +0.0 will no longer be exactly equ...
___________________________________________________________________
In Erlang/OTP 27, +0.0 will no longer be exactly equal to -0.0
Author : todsacerdoti
Score : 86 points
Date : 2023-05-09 19:58 UTC (3 hours ago)
(HTM) web link (erlangforums.com)
(TXT) w3m dump (erlangforums.com)
| EdSchouten wrote:
| Quite amazing how so many environments of floating point values
| make the mistake of treating them as equal. YAML 1.2 does the
| same thing:
|
| https://yaml.org/spec/1.2.2/#10214-floating-point
| murderfs wrote:
| Is it surprising that so many environments that use IEEE 754
| floating point numbers obey IEEE 754 on equality?
|
| IEEE 754-2008 5.11: "Comparisons shall ignore the sign of zero
| (so +0 = -0)."
| ilyt wrote:
| Because they are equal in real math
| Taniwha wrote:
| Yes but this is floating point - +0.0 represents all numbers
| between 0 and the smallest +ve FP number, -0.0 represents all
| numbers between 0 and the smallest -ve FP number - both can
| represent actual 0 - remember these can be generated by
| underflow so those meanings can be important in context
| zokier wrote:
| Floats are not real numbers though
| garbagecoder wrote:
| They are a way of approximating Real (the set not as an
| adjective) numbers, though. Real numbers are exact when
| they are equal to an integer or a rational, because,
| depending on how you construct them, either that's what the
| cut contains or that's where the series converges.
|
| And also because that's what the most common spec demands.
| recursive wrote:
| Floats are a subset of the reals.
| [deleted]
| yifanl wrote:
| floating point math is math like any other, just with a
| vastly different number system. There are valid arguments on
| both side if we should hide or expose those differences in
| the implementation, but there's always going to be pretty big
| implications (i.e. the compiler behaviour mentioned here)
| codegeek wrote:
| -0.0 = 0
|
| +0.0 = 0
|
| Hence -0.0 = +0.0 right ?
| cesaref wrote:
| As a general rule, if you find yourself comparing floating point
| numbers, that's probably not what you want.
|
| I'm wondering what they are going to do with other operators, >=,
| <= etc
| flatline wrote:
| It is 2023 and our tooling still encourages the same mistakes
| people were making 40 years ago. Can we really not have
| equality operators that do a comparison with a 1% tolerance or
| something as a sensible default equivalence instead of
| blatantly wrong bitwise comparison?
|
| I would even be happy with a default set of compiler warnings
| or errors, which I don't believe I have ever seen.
| colejohnson66 wrote:
| The problem with defining an "epsilon" (1% in your case) is
| that there is no value that would please everyone. For some,
| 1% will be fine, but others may need 0.0001%.
|
| The solution is to either use decimal floats if they suit
| your need (and eat the performance penalty), or to use a
| linter that flags float comparisons by equality.
| dzaima wrote:
| And then there's the question of how you use the epsilon -
| i.e. whether 1.2e-16 and 1e-20 are "within 1%". Sometimes
| those are very much different sizes, but if you're
| comparing sin(p) (i.e. the 1.2e-16) with some other almost-
| zero computation, they're very much within 1%.
| chubot wrote:
| From a user POV, I think == and === on floats should simply
| be undefined in any language. It should be a compile time or
| runtime error.
|
| There can be a separate function `float_equals()` with
| explicit args that does what people want
|
| The only reason to use the same syntax == is for POLYMORPHIC
| code that is actually correct.
|
| But it's not going to be correct with floats, because they
| don't obey the same algebraic laws ... So the syntax should
| be different!
|
| ---
|
| I believe the Erlang compiler optimization presented as
| justification for this change is a good example
|
| The compiler wants to reason about code, independent of types
|
| But that reasoning about the =:= operator is wrong for the
| float case.
| regularfry wrote:
| I've always held that the numbers themselves are perfectly
| precise. It's the operations that don't do what you expect.
|
| Of course, that observation may be more or less useful,
| depending on circumstances.
| ChainOfFools wrote:
| Somewhere in the distant past a second grade me is staring at
| his math homework and fuming over the frustratingly ambiguous
| meaning of the minus sign. Is it an operator? A property?
| Both?
| thr-nrg wrote:
| Math notation is to mathematics as poetry is to English.
|
| The only acceptable grammar for math is s-expressions.
| atemerev wrote:
| We don't have fast hardware decimals, except on IBM mainframes.
| So, if you need fast financial calculations which are too
| complicated for integers (e.g. _gasp_ division), you usually
| use decimally-normalized doubles and do it very very carefully.
|
| This is a sad state of affairs, but nobody was fixing this in
| the last 30 years, so there's that.
| cubefox wrote:
| Are there even any applications where one needs a) decimal
| precision and b) fast computation? Financial calculations
| don't need to be fast.
| nvy wrote:
| High-frequency trading, perhaps.
|
| Physics simulations too.
| wtallis wrote:
| Physics simulations are a great example of where there's
| obviously no need to do arithmetic in decimal rather than
| binary floating point.
| atemerev wrote:
| Algorithmic trading, of course. High-frequency trading in
| particular. But any trading, in fact, when you run
| simulations for thousands of instruments and search among
| millions of parameters. Or when you implement an exchange,
| or route orders between exchanges according to their
| complicated criteria. Or when you do options pricing.
| Basically, everything in this field.
| [deleted]
| justeleblanc wrote:
| So something that produces nothing of value to
| civilization. Gotcha.
| atemerev wrote:
| Well, I assume you want to sell your hard-earned stocks
| of civilization-helping companies sometimes...
| justeleblanc wrote:
| Nope. I'd rather there not be a thing called a stock
| market at all.
| zwischenzug wrote:
| Gambling.
| mamcx wrote:
| Business apps? Like all of them?
|
| As matter of fact, business apps are by far the longest
| portion of the apps. Just considering all spreadsheets,
| almost all RDBMS, etc alone.
|
| Binary Floating-point is what is ACTUALLY niche. And it not
| look like it just because is the default, similar how in
| certain niches "0" is "true".
| FpUser wrote:
| I would add generating large financial reports, enterprise
| billing etc. There is a huge business effect between for
| example 2 hours needed to calculate monthly invoices and 24
| hours.
| wtallis wrote:
| Is any of that actually bottlenecked by arithmetic rather
| than pointer chasing?
| sterlind wrote:
| I'm assuming you mean equality comparison, rather than
| comparison in general!
|
| but is there a better algebraic structure to use when thinking
| of floats? like, in terms of limits, or something that tracks
| significant digits and uncertainty? how do formal methods
| handle these?
| Y_Y wrote:
| There's more than one way to skin that cat, but interval
| arithmetic is a good and simple model. You can take the part
| of the real line which maps to the single float you are
| thinking about, and then look at the image of that set under
| the whatever functions you are thinking about.
| wslh wrote:
| Floating point numbers are dangerous but in 2023 we should find
| a way to improve the side effects.
|
| Playing with Python3:
|
| >>> +0.0==-0.0 True
|
| The C in GCC below also returns 1:
|
| #include <stdio.h>
|
| int main()
|
| { float a = +0.0; float b =
| -0.0; printf("%d\n", a == b); }
| Y_Y wrote:
| Erlang too, now and in the future will say that
| minuszero==zero, it's a different more specific operator
| that's changing.
|
| You'll get the same in python with
|
| >>> -0. is 0.
|
| False
| Kwpolska wrote:
| The `is` operator is checking object identity by comparing
| addresses. It is useless, although it may sometimes produce
| reasonable-looking results due to optimisations.
|
| Python 3.11 gives me this:
|
| >>> -0 is 0 <stdin>:1: SyntaxWarning: "is" with a literal.
| Did you mean "=="? True
|
| Because (a) -0 is an integer, and there is only one integer
| zero, (b) small numbers have only one instance in memory as
| an optimization.
| toast0 wrote:
| Since they're not changing ==, I wouldn't think =< and >= would
| change. Note, Erlang has a no arrow looking comparison rule, so
| <= is not a comparison operator; actually it's used in
| bitstring comprehensions, the bitstring version of list
| comprehensions which use <- [1]
|
| [1]
| https://www.erlang.org/doc/reference_manual/expressions.html...
| abhgh wrote:
| As a followup to this comment, in Python you have the option of
| using `isclose()` which is present in both `numpy`[1] and the
| standard `math` [2] libraries. This has been quite helpful for
| me in comparing small probability values.
|
| [1]
| https://numpy.org/doc/stable/reference/generated/numpy.isclo...
|
| [2] https://docs.python.org/3/library/math.html#math.isclose
| ssivark wrote:
| This smells like the right way to think about things. Whenever
| thinking of real life measurements represented as real numbers,
| equality is always fuzzy -- up to some resolution, and it would
| be better to make that explicit.
| nawgz wrote:
| > if you find yourself comparing floating point numbers, that's
| probably not what you want
|
| Can you motivate this with an example for me? For instance, I
| think of games or location data encoded as FP; then, clearly,
| comparing them is a critical task to know questions like "what
| is closer" and so on.
|
| What am I missing?
| stefncb wrote:
| They meant using the equal operator, because floating point
| is inexact and can produce different representations for the
| same number depending on how it was obtained. Greater and
| less than are fine. Equal is usually implemented by seeing if
| a number fits inside a tight range.
| recursive wrote:
| Every finite floating point value precisely represents an
| exact quantity. Arithmetic isn't lossless though. This
| isn't a special property of floats. Decimals behave the
| same way.
| zokier wrote:
| Common way of comparing floats is to compare the difference
| to some epsilon value. See Python PEP-485 for example
| https://peps.python.org/pep-0485/
| [deleted]
| orthoxerox wrote:
| This is specifically about equality. Comparing FPs for
| equality is very risky, as your numbers can differ by
| 0.00000000000001 without anyone noticing.
|
| Strict inequality (> and <) comparison are generally fine as
| long as you avoid NaNs.
| nawgz wrote:
| Ok, sure, so it was just for a specific case of comparison.
|
| Well, followup, if some error Epsilon can be introduced
| during any manipulations, when you check for strict
| inequalities do you check x < y + Epsilon? Does the
| language implicitly do it for you?
| klodolph wrote:
| The language just gives you the direct comparison.
|
| If you know numerics, then you can come up with the
| correct value for epsilon. But that is hard work. There
| are various things that the language could do for you,
| like use a dynamic amount of precision, or interval
| arithmetic where the error bounds are saved--but! Most of
| the time people just want the answer faster and with less
| memory used, which is what you get with bare floats. The
| people who make it their job to care about numerical
| accuracy can do it better than the language runtime would
| anyway.
|
| Most of the problems are more easily solvable at a higher
| level anyway. Like, imagine Clippy saying to you, "It
| looks like you're inverting a matrix. Are you sure that's
| a good idea?"
| HexDecOctBin wrote:
| Ideally, you'll want to do distance calculations in fixed
| point numbers (of desired resolution). Floating point works
| well as a first approximation, but unless you can make sure
| that you are not going to end up wandering in the weeds (and
| ensuring that required understanding of computational
| numerics), you should probably replace them with fixed point
| once you understand the problem and the solution and know the
| limits involved.
| zdragnar wrote:
| Caveat: In the following, I use "comparison" to mean "check
| for equality".
|
| Floating point numbers lose precision because binary
| arithmetic doesn't represent decimal in all cases ( 1/3 is an
| easy example). It's not hard to get into a situation where
| you're asking if a number is 0.0 but due to a precision error
| the number you have is 0.00000000000001 or whatever and it
| _should_ have been zero.
|
| If you're dealing with anything where precision is paramount
| (i.e. money or high precision machining) you should consider
| using something else that matches the real work precision you
| are trying to model.
|
| You can, of course, use floats and test differences rather
| than strict equality (i.e. distance is < 0.01) and that's
| fine too... _if_ you remember to do it, and account for the
| potential for small precision differences to propagate
| throughout your calculations and accumulate into larger
| errors.
| tobr wrote:
| > Caveat: In the following, I use "comparison" to mean
| "check for equality".
|
| You never use the word "comparison" again in the comment!
| jandrese wrote:
| IMHO That was poorly worded. What it should have said is "if
| you find yourself using equals to compare floating point
| numbers...". With Floating Point your comparisons should
| always be less than or greater than. Precision artifacts make
| the equals unreliable, and you should always be mindful of
| that when dealing with them.
| BlueTemplar wrote:
| It's the hard task of trying to figure out the magnitude of
| the expected errors (which can accumulate), in the simplest
| case of a single operation you compare within epsilon
| distance :
|
| https://en.wikibooks.org/wiki/Floating_Point/Epsilon
| jbgreer wrote:
| As someone who programmed assembly on a one's complement machine*
| 40 years ago, this discussion is interesting, from both the
| "haven't we learned anything" and the "makes sense to me" camps.
|
| More interesting to me is how they are introducing this change,
| both in the previous OTP and next, and how they will arm people
| with tools to hopefully identify and understand the impact. I
| wonder how many folks will actually be impacted?
|
| * Sperry Univac 1100/62
| bjourne wrote:
| Negative zero is very, very annoying because it means that many
| floating point identities flies out of the window. x + 0.0 != x,
| x * 0.0 != 0.0, etc. It makes it much harder for the compiler to
| optimize arithmetic. Thus, at least some SIMD circuits do not
| follow ieee754 to the letter because it would result in
| performance degradations.
| pklausler wrote:
| I find it so strange that IEEE-754 SQRT(-0.) is -0., even
| though (-0.)*(-0.) is +0.
| stkdump wrote:
| Those identities do hold though, as long as zero and negative
| zero compare equal, which according to ieee754 they should.
| Performance degredations happen in other places such as
| denormals.
| yccs27 wrote:
| Can anyone fill me in what the meaning of == vs =:= in Erlang is?
| Since after the change +0. == -0. but not +0. =:= -0.
| ollien wrote:
| =:= is supposed to be "exactly equals". An example given in the
| docs[1]: 1> 1==1.0. true 2>
| 1=:=1.0. false
|
| You can think of it as analogous to == vs === in JS, except
| that the "type safety" here really only refers to different
| numeric types (and in JS there is no such distinction)
|
| [1]:
| https://www.erlang.org/doc/reference_manual/expressions.html...
| duskwuff wrote:
| This feels like a bad idea. Treating +0 and -0 as unequal because
| they have different bit patterns is no more correct than treating
| two NaNs as equal because they have the same bit pattern. Zero is
| zero.
| SoftTalker wrote:
| What Every Programmer Should Know About Floating-Point
| Arithmetic
|
| https://floating-point-gui.de/
| alerighi wrote:
| In case of floating points, since they are an approximation,
| zero is not zero. Zero is a quantity approaching to zero. In
| fact dividing by zero with floats is entirely possible! (it
| leads to infinite, positive or negative depending on the sign).
|
| For example, the quantity (try it in Python):
| 0.1 ** 100 / 1000**100 evaluates to 0.0
|
| while the quantity 0.1 ** 100 / (-1000**100)
| evaluates to -0.0
|
| It's clear that neither the two quantities are 0 (no division
| can produce a result that is exactly zero!) but they approach
| the zero. But one quantity is slightly less than zero, the
| other slightly more than zero.
|
| Of course for integers -0 doesn't make sense (that is the
| reason why we invented 2 complement, to not have 2 "zeros")
| parasti wrote:
| That sounds wrong to me. In IEEE floating point, 0.0 is not
| an approximation of zero, it is zero exactly. The binary
| value is literally all zero bits. Negative zero appears
| simply from the IEEE floating point format reserving a bit
| for the sign.
| ghayes wrote:
| > no division can produce a result that is exactly zero
|
| Except 0 / n
| phoe-krk wrote:
| Or -0.0 / n, for what it's worth, at which point these
| zeroes will not be equal to one another.
| recursive wrote:
| Or 1e-100 / 1e300
| jbverschoor wrote:
| Unless you're approaching the limit of 0 from one side
|
| But yeah, you're right
| andrewmcwatters wrote:
| Zero is zero, but signs are a direction. There are multiple
| parts to numbers.
|
| Stand in place and turn around: how far did you go from where
| you started?
|
| Nonetheless, I agree. It's a bad idea.
| quietbritishjim wrote:
| By that logic there should be e.g. +4.0 and +4.0- (for when
| you go 4m forwards vs when you go 4m forwards but also turn
| around at the end).
| mananaysiempre wrote:
| IEEE 754's idea was that if you got a zero in a floating-point
| calculation (which, at the time, meant one with physical
| quantities), it's probably underflow, not a legitimate result,
| so the FPU might as well try to give you at least a sign even
| if it can't give you any significant digits. Though 754 says
| comparison must treat positive and negative zero as equal
| nevertheless, you have to deliberately choose to look at the
| sign (with e.g. C99's copysign) to see the difference.
| zokier wrote:
| The word 'exactly', omitted from HN title, is relevant here:
|
| > The == operator will continue to return true for 0.0 == -0.0.
| dang wrote:
| We've re-exactlied it. Thanks!
| ComplexSystems wrote:
| There is a huge misconception here which really should be
| clarified in the title. Erlang has an "exactly equal" or strict
| equality operator, called =:=, which is different from just usual
| equality, which is ==. The usual equality operator will keep
| having +0.0 = -0.0, and floating point arithmetic will keep
| behaving as you would expect... It's that we no longer have +0.0
| =:= -0.0, which is a very different thing (and frankly, makes
| sense - there _should_ be some special operator that can
| differentiate between these two values).
| Trufa wrote:
| What's the use case for different values?
| lliamander wrote:
| It's a natural consequence of the IEEE 754 standard for
| floating point numbers.
| pmarreck wrote:
| Because two binary values that are not bit-for-bit equal
| should have an equality operator that can reflect that
| without resorting to conversion
___________________________________________________________________
(page generated 2023-05-09 23:00 UTC)