Skip to content

Instantly share code, notes, and snippets.

@haasted
Created February 27, 2019 12:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save haasted/e3ccc703928b86554b331bb4a4eee5b2 to your computer and use it in GitHub Desktop.
Save haasted/e3ccc703928b86554b331bb4a4eee5b2 to your computer and use it in GitHub Desktop.
Go version of the nearlyEqual function for float values described at https://floating-point-gui.de/errors/comparison/
package main
import (
"math"
"testing"
"github.com/stretchr/testify/assert"
)
var (
minNormal = math.Float32frombits(0x00800000) // Similar to Float.MIN_NORMAL in java
)
// NearlyEqualPrecision32 implements a float "equality" check based on the instructions at https://floating-point-gui.de/errors/comparison/
func NearlyEqualPrecision32(a, b, epsilon float32) bool {
absA := abs32(a)
absB := abs32(b)
diff := abs32(a - b)
if a == b { // shortcut, handles infinities
return true
} else if a == 0 || b == 0 || diff < minNormal {
// a or b is zero or both are extremely close to it
// relative error is less meaningful here
return diff < (epsilon * minNormal)
} else { // use relative error
min := float32(math.Min(float64(absA+absB), math.MaxFloat32))
return diff/min < epsilon
}
}
// NearlyEqual32 calls NearlyEqualPrecision with a pre-defined epsilon value.
func NearlyEqual32(a, b float32) bool {
return NearlyEqualPrecision32(a, b, 0.00001)
}
func abs32(v float32) float32 {
if v < 0 {
v = -v
}
return v
}
// Test cases from https://floating-point-gui.de/errors/NearlyEqualsTest.java
var (
NaN = float32(math.NaN())
PositiveInfinity = float32(math.Inf(1))
NegativeInfinity = float32(math.Inf(-1))
minNormal32 = math.Float32frombits(0x00800000) // Similar to Float.MIN_NORMAL in java
minValue32 = math.Float32frombits(0x1) // Similar to Float.MIN_VALUE in java
)
func TestBig(t *testing.T) {
/** Regular large numbers - generally not problematic */
assert.True(t, NearlyEqual32(1000000.0, 1000001.0))
assert.True(t, NearlyEqual32(1000001, 1000000))
assert.False(t, NearlyEqual32(10000, 10001))
assert.False(t, NearlyEqual32(10001, 10000))
}
func TestBigNeg(t *testing.T) {
/** Negative large numbers */
assert.True(t, NearlyEqual32(-1000000, -1000001))
assert.True(t, NearlyEqual32(-1000001, -1000000))
assert.False(t, NearlyEqual32(-10000, -10001))
assert.False(t, NearlyEqual32(-10001, -10000))
}
func TestMid(t *testing.T) {
/** Numbers around 1 */
assert.True(t, NearlyEqual32(1.0000001, 1.0000002))
assert.True(t, NearlyEqual32(1.0000002, 1.0000001))
assert.False(t, NearlyEqual32(1.0002, 1.0001))
assert.False(t, NearlyEqual32(1.0001, 1.0002))
}
func TestMidNeg(t *testing.T) {
/** Numbers around -1 */
assert.True(t, NearlyEqual32(-1.000001, -1.000002))
assert.True(t, NearlyEqual32(-1.000002, -1.000001))
assert.False(t, NearlyEqual32(-1.0001, -1.0002))
assert.False(t, NearlyEqual32(-1.0002, -1.0001))
}
func TestSmall(t *testing.T) {
/** Numbers between 1 and 0 */
assert.True(t, NearlyEqual32(0.000000001000001, 0.000000001000002))
assert.True(t, NearlyEqual32(0.000000001000002, 0.000000001000001))
assert.False(t, NearlyEqual32(0.000000000001002, 0.000000000001001))
assert.False(t, NearlyEqual32(0.000000000001001, 0.000000000001002))
}
func TestSmallNeg(t *testing.T) {
/** Numbers between -1 and 0 */
assert.True(t, NearlyEqual32(-0.000000001000001, -0.000000001000002))
assert.True(t, NearlyEqual32(-0.000000001000002, -0.000000001000001))
assert.False(t, NearlyEqual32(-0.000000000001002, -0.000000000001001))
assert.False(t, NearlyEqual32(-0.000000000001001, -0.000000000001002))
}
func TestSmallDiffs(t *testing.T) {
/** Small differences away from zero */
assert.True(t, NearlyEqual32(0.3, 0.30000003))
assert.True(t, NearlyEqual32(-0.3, -0.30000003))
}
func TestZero(t *testing.T) {
/** Comparisons involving zero */
assert.True(t, NearlyEqual32(0.0, 0.0))
assert.True(t, NearlyEqual32(0.0, -0.0))
assert.True(t, NearlyEqual32(-0.0, -0.0))
assert.False(t, NearlyEqual32(0.00000001, 0.0))
assert.False(t, NearlyEqual32(0.0, 0.00000001))
assert.False(t, NearlyEqual32(-0.00000001, 0.0))
assert.False(t, NearlyEqual32(0.0, -0.00000001))
assert.True(t, NearlyEqualPrecision32(0.0, 1e-40, 0.01))
assert.True(t, NearlyEqualPrecision32(1e-40, 0.0, 0.01))
assert.False(t, NearlyEqualPrecision32(1e-40, 0.0, 0.000001))
assert.False(t, NearlyEqualPrecision32(0.0, 1e-40, 0.000001))
assert.True(t, NearlyEqualPrecision32(0.0, -1e-40, 0.1))
assert.True(t, NearlyEqualPrecision32(-1e-40, 0.0, 0.1))
assert.False(t, NearlyEqualPrecision32(-1e-40, 0.0, 0.00000001))
assert.False(t, NearlyEqualPrecision32(0.0, -1e-40, 0.00000001))
}
func TestExtemeMax(t *testing.T) {
assert.True(t, NearlyEqual32(math.MaxFloat32, math.MaxFloat32))
assert.False(t, NearlyEqual32(math.MaxFloat32, -math.MaxFloat32))
assert.False(t, NearlyEqual32(-math.MaxFloat32, math.MaxFloat32))
assert.False(t, NearlyEqual32(math.MaxFloat32, math.MaxFloat32/2))
assert.False(t, NearlyEqual32(math.MaxFloat32, -math.MaxFloat32/2))
assert.False(t, NearlyEqual32(-math.MaxFloat32, math.MaxFloat32/2))
}
func TestInfinities(t *testing.T) {
PositiveInfinity := float32(math.Inf(1))
NegativeInfinity := float32(math.Inf(-1))
assert.True(t, NearlyEqual32(PositiveInfinity, PositiveInfinity))
assert.True(t, NearlyEqual32(NegativeInfinity, NegativeInfinity))
assert.False(t, NearlyEqual32(NegativeInfinity, PositiveInfinity))
assert.False(t, NearlyEqual32(PositiveInfinity, math.MaxFloat32))
assert.False(t, NearlyEqual32(NegativeInfinity, -math.MaxFloat32))
}
func TestNan(t *testing.T) {
/** Comparisons involving NaN values */
assert.False(t, NearlyEqual32(NaN, NaN))
assert.False(t, NearlyEqual32(NaN, 0.0))
assert.False(t, NearlyEqual32(-0.0, NaN))
assert.False(t, NearlyEqual32(NaN, -0.0))
assert.False(t, NearlyEqual32(0.0, NaN))
assert.False(t, NearlyEqual32(NaN, PositiveInfinity))
assert.False(t, NearlyEqual32(PositiveInfinity, NaN))
assert.False(t, NearlyEqual32(NaN, NegativeInfinity))
assert.False(t, NearlyEqual32(NegativeInfinity, NaN))
assert.False(t, NearlyEqual32(NaN, math.MaxFloat32))
assert.False(t, NearlyEqual32(math.MaxFloat32, NaN))
assert.False(t, NearlyEqual32(NaN, -math.MaxFloat32))
assert.False(t, NearlyEqual32(-math.MaxFloat32, NaN))
assert.False(t, NearlyEqual32(NaN, minValue32))
assert.False(t, NearlyEqual32(minValue32, NaN))
assert.False(t, NearlyEqual32(NaN, -minValue32))
assert.False(t, NearlyEqual32(-minValue32, NaN))
}
func TestOpposite(t *testing.T) {
/** Comparisons of numbers on opposite sides of 0 */
assert.False(t, NearlyEqual32(1.000000001, -1.0))
assert.False(t, NearlyEqual32(-1.0, 1.000000001))
assert.False(t, NearlyEqual32(-1.000000001, 1.0))
assert.False(t, NearlyEqual32(1.0, -1.000000001))
assert.True(t, NearlyEqual32(10*minValue32, 10*-minValue32))
assert.False(t, NearlyEqual32(10000*minValue32, 10000*-minValue32))
}
func TestUlp(t *testing.T) {
/** The really tricky part - comparisons of numbers very close to zero. */
assert.True(t, NearlyEqual32(minValue32, minValue32))
assert.True(t, NearlyEqual32(minValue32, -minValue32))
assert.True(t, NearlyEqual32(-minValue32, minValue32))
assert.True(t, NearlyEqual32(minValue32, 0))
assert.True(t, NearlyEqual32(0, minValue32))
assert.True(t, NearlyEqual32(-minValue32, 0))
assert.True(t, NearlyEqual32(0, -minValue32))
assert.False(t, NearlyEqual32(0.000000001, -minValue32))
assert.False(t, NearlyEqual32(0.000000001, minValue32))
assert.False(t, NearlyEqual32(minValue32, 0.000000001))
assert.False(t, NearlyEqual32(-minValue32, 0.000000001))
}
@brazzy
Copy link

brazzy commented Jun 18, 2019

Note there's a bug in this code, see brazzy/floating-point-gui.de#37

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment