Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Go float comparison

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.

like image 806
augustzf Avatar asked Dec 25 '17 14:12

augustzf


2 Answers

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))
}
like image 127
Arman Ordookhani Avatar answered Nov 14 '22 14:11

Arman Ordookhani


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.

like image 31
Eric Postpischil Avatar answered Nov 14 '22 13:11

Eric Postpischil