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.
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))
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With