[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)