diff --git a/prometheus/histogram_test.go b/prometheus/histogram_test.go index 4c3d2e084..05ae617f6 100644 --- a/prometheus/histogram_test.go +++ b/prometheus/histogram_test.go @@ -28,6 +28,8 @@ import ( "github.com/golang/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" + "github.com/prometheus/client_golang/prometheus/internal" + dto "github.com/prometheus/client_model/go" ) @@ -358,9 +360,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 +468,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