From b481d09f478ca8f209b4d2387d045e682bd0f148 Mon Sep 17 00:00:00 2001 From: Seth Bunce Date: Tue, 27 Sep 2022 08:35:15 -0700 Subject: [PATCH] copy float compare dependency Per discussion in the pull request, we'd like to avoid having an extra dependency on a float comparison package. Instead, we copy the float compare functions from the float comparison package. The float comparison package we're choosing is this. The author of this package has commented in the pull request and it looks like we have consensus that this is the best option. github.com/beorn7/floats --- prometheus/histogram_test.go | 27 ++-------- prometheus/internal/difflib.go | 92 ++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 23 deletions(-) diff --git a/prometheus/histogram_test.go b/prometheus/histogram_test.go index 4c3d2e084..c5e5585f5 100644 --- a/prometheus/histogram_test.go +++ b/prometheus/histogram_test.go @@ -26,6 +26,7 @@ import ( //nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility. "github.com/golang/protobuf/proto" + "github.com/prometheus/client_golang/prometheus/internal" "google.golang.org/protobuf/types/known/timestamppb" dto "github.com/prometheus/client_model/go" @@ -358,9 +359,9 @@ func TestBuckets(t *testing.T) { 1.0, 1.6681, 2.7825, 4.6415, 7.7426, 12.9154, 21.5443, 35.9381, 59.9484, 100.0000, } - const tolerance = 0.0001 - if !equalFloat64s(got, want, tolerance) { - t.Errorf("exponential buckets range: got %v, want %v (tolerance %f)", got, want, tolerance) + const epsilon = 0.0001 + if !internal.AlmostEqualFloat64s(got, want, epsilon) { + t.Errorf("exponential buckets range: got %v, want %v (epsilon %f)", got, want, epsilon) } } @@ -466,23 +467,3 @@ func TestHistogramExemplar(t *testing.T) { } } } - -// equalFloat64 returns true if a and b are within the tolerance. We have this -// because float comparison varies on different architectures. For example, -// architectures which do FMA yield slightly different results. -// https://github.com/prometheus/client_golang/pull/899#issuecomment-1244506390 -func equalFloat64(a, b, tolerance float64) bool { - return math.Abs(a-b) < tolerance -} - -func equalFloat64s(a, b []float64, tolerance float64) bool { - if len(a) != len(b) { - return false - } - for i := range a { - if !equalFloat64(a[i], b[i], tolerance) { - return false - } - } - return true -} diff --git a/prometheus/internal/difflib.go b/prometheus/internal/difflib.go index fd45cadc0..1ab88fe52 100644 --- a/prometheus/internal/difflib.go +++ b/prometheus/internal/difflib.go @@ -22,6 +22,7 @@ import ( "bytes" "fmt" "io" + "math" "strings" ) @@ -46,6 +47,97 @@ func calculateRatio(matches, length int) float64 { return 1.0 } +var ( + // minNormalFloat64 is the smallest positive normal value of type float64. + minNormalFloat64 = math.Float64frombits(0x0010000000000000) + + // minNormalFloat32 is the smallest positive normal value of type float32. + minNormalFloat32 = math.Float32frombits(0x00800000) +) + +// AlmostEqualFloat64 returns true if a and b are equal within a relative error +// of epsilon. See http://floating-point-gui.de/errors/comparison/ for the +// details of the applied method. +// +// This function is copy/paste to avoid a dependency. +// https://github.com/beorn7/floats +func AlmostEqualFloat64(a, b, epsilon float64) bool { + if a == b { + return true + } + absA := math.Abs(a) + absB := math.Abs(b) + diff := math.Abs(a - b) + if a == 0 || b == 0 || absA+absB < minNormalFloat64 { + return diff < epsilon*minNormalFloat64 + } + return diff/math.Min(absA+absB, math.MaxFloat64) < epsilon +} + +// AlmostEqualFloat64s is the slice form of AlmostEqualFloat64. +func AlmostEqualFloat64s(a, b []float64, epsilon float64) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if !AlmostEqualFloat64(a[i], b[i], epsilon) { + return false + } + } + return true +} + +// AlmostEqualFloat32 returns true if a and b are equal within a relative error +// of epsilon. See http://floating-point-gui.de/errors/comparison/ for the +// details of the applied method. +// +// This function is copy/paste to avoid a dependency. +// https://github.com/beorn7/floats +func AlmostEqualFloat32(a, b, epsilon float32) bool { + if a == b { + return true + } + absA := AbsFloat32(a) + absB := AbsFloat32(b) + diff := AbsFloat32(a - b) + if a == 0 || b == 0 || absA+absB < minNormalFloat32 { + return diff < epsilon*minNormalFloat32 + } + return diff/MinFloat32(absA+absB, math.MaxFloat32) < epsilon +} + +// AlmostEqualFloat32s is the slice form of AlmostEqualFloat32. +func AlmostEqualFloat32s(a, b []float32, epsilon float32) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if !AlmostEqualFloat32(a[i], b[i], epsilon) { + return false + } + } + return true +} + +// AbsFloat32 works like math.Abs, but for float32. +func AbsFloat32(x float32) float32 { + switch { + case x < 0: + return -x + case x == 0: + return 0 // return correctly abs(-0) + } + return x +} + +// MinFloat32 works like math.Min, but for float32. +func MinFloat32(x, y float32) float32 { + if x < y { + return x + } + return y +} + type Match struct { A int B int