Go float comparison

26,141

Solution 1

No, this is not the correct way compare floating-point values.

You have not actually stated your real problem—there is some reason you are trying to compare two floating-point numbers, but you have not said what it is.

Floating-point arithmetic is designed to perform approximate arithmetic. It is normal that there will be an accumulation of rounding errors in floating-point operations. These errors will generally be different when values are calculated in different ways, so floating-point arithmetic should not be expected to produce equal results.

In your example, these operations occurred:

  • The decimal numeral “0.1” was converted to float64 (IEEE-754 64-bit binary floating-point). This produced the value 0.1000000000000000055511151231257827021181583404541015625, which is the closest float64 value to 0.1.

  • The decimal numeral “0.2” was converted to float64. This produced 0.200000000000000011102230246251565404236316680908203125, which is the closest float64 value to 0.2.

  • These were added. This produced 0.3000000000000000444089209850062616169452667236328125. In addition to the rounding errors that occurred when 0.1 and 0.2 were rounded to the nearest values in float64, this contains some additional rounding error because the exact sum cannot be represented in float64.

  • The decimal numeral “0.3” was converted to float64. This produced 0.299999999999999988897769753748434595763683319091796875, which is the closest float64 value to 0.3.

As you can see, the result of adding 0.1 and 0.2 has accumulated different rounding errors from 0.3, so they are unequal. No correct test for equality will report they are equal. And, importantly, the errors that occurred in this example are specific to this example—different sequences of floating-point operations will have different errors, and the accumulated errors are not limited to the low bits of the numbers.

Some people try to compare by testing whether the difference is less than some small value. This is can be okay in some applications, but is it okay in your application? We do not know what you are trying to do, so we do not know what problems will occur. Tests that allow for a small error sometimes report incorrect results, either false positives (because they accept as equal numbers that would not be equal if computed with exact mathematics) or false negatives (because they reject equality for numbers that would be equal if computed with exact mathematics). Which of these errors is worse for your application? Will one of them cause a machine to break or a person to be harmed? Without knowing that, nobody can advise which incorrect result is acceptable, or even if either is.

Additionally, how large should the tolerance for error be? The total error that can occur in a calculation depends on the sequence of operations performed and the numbers involved. Some applications will have only a small final rounding error, and some applications can have massive errors. Nobody can give a recommendation about what value to use for the tolerance without knowing more about your specific sequence of operations. Also, the solution might not be to accept a tolerance in comparing numbers but to redesign your calculations to avoid error, or at least to reduce it.

No general solution for comparing floating-point values for “equality” exists because it is impossible for any such solution to exist.

Solution 2

Don't use bit representation of float64 as it don't make sense in a lot of cases. Just subtract two numbers to find out how much they differ:

package main

import (
    "fmt"
    "math"
)

const float64EqualityThreshold = 1e-9

func almostEqual(a, b float64) bool {
    return math.Abs(a - b) <= float64EqualityThreshold
}

func main() {
    a := 0.1
    b := 0.2
    fmt.Println(almostEqual(a + b, 0.3))
}
Share:
26,141
augustzf
Author by

augustzf

Developer and entrepreneur

Updated on July 12, 2022

Comments

  • augustzf
    augustzf almost 2 years

    In order to compare two floats (float64) for equality in Go, my superficial understanding of IEEE 754 and binary representation of floats makes me think that this is a good solution:

    func Equal(a, b float64) bool {
        ba := math.Float64bits(a)
        bb := math.Float64bits(b)
        diff := ba - bb
        if diff < 0 {
            diff = -diff
        }
        // accept one bit difference
        return diff < 2
    }
    

    The question is: Is this a more generic, more precise, and more efficient, way to compare two arbitrarily large or small floats for "almost equalness", than the old abs(diff) < epsilon hack? My reasoning being that if one allows only one bit difference in the binary representation, then the compared numbers certainly could not be any more equal, apart from strict equality, which obviously (as pointed out in the comments) can be checked with == for floats.

    Note: I have edited the question to make it more clear.

  • kostix
    kostix over 6 years
  • Arman Ordookhani
    Arman Ordookhani over 6 years
    @augustzf You could use something like math.Abs(a - b) <= float64EqualityThreshold * (math.Abs(a) + math.Abs(b)) . @kostix link seems even better.
  • Eric Postpischil
    Eric Postpischil over 6 years
    @augustzf: There is no good generic comparison, and no good generic comparison is possible.
  • augustzf
    augustzf over 6 years
    I have edited the question now. I agree that for concrete applications, one probably knows what tolerance is acceptable and should define equality with that in mind. But my question is less application-specific and more about what can be achieved by inspecting the underlying bit representation of the numbers.
  • Eric Postpischil
    Eric Postpischil over 6 years
    @augustzf: Essentially nothing useful in this situation can be achieved by inspecting the bits. Generally, any useful tests can be performed with normal floating-point operations. If you are just wondering how floating-point works and should be used, rather than trying to solve a specific application problem, then the answer is that floating-point is not designed for this.
  • augustzf
    augustzf over 6 years
    Yes, I've come to the same conclusion now after reading an this article on the same topic.
  • Arman Ordookhani
    Arman Ordookhani over 6 years
    @EricPostpischil I understand what you're saying but "no good generic" seems a bit much too me. Simple almostEqual functions can be used in most of situations. If somebody don't tolerate aggregated tiny errors he/she should not use float point calculation at all.
  • Eric Postpischil
    Eric Postpischil over 6 years
    @ArmanOrdookhani: They can be used, but they are not good. Most Stack Overflow questions about comparing for equality in floating-point arise when somebody is experimenting with floating-point as a learning exercise. They do not have a real use. The only others I recall showing an actual need are unit-testing. (I answered one here.) In that case, we can bound the errors due to floating-point rounding and trust that it is lower than errors due to bugs, so there is a clear value we can use for the tolerance to make a useful comparison.
  • Eric Postpischil
    Eric Postpischil over 6 years
    @ArmanOrdookhani: Other than that, answers that show “methods” to do floating-point comparison generally use bad examples. They use arbitrary tolerances that are not generally applicable, sometimes using FLT_EPSILON or DBL_EPSILON improperly (e.g., not scaled for the magnitudes of the numbers involved, which is only one of the errors made with them), arbitrarily switching between absolute or relative error because the author worked out something that kind of worked, not due to any theory of operation, and so on. They are kludges, not good generic solutions.