Skip to content

Commit

Permalink
copy float compare dependency
Browse files Browse the repository at this point in the history
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
  • Loading branch information
sbunce committed Sep 27, 2022
1 parent 0d8d662 commit b481d09
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 23 deletions.
27 changes: 4 additions & 23 deletions prometheus/histogram_test.go
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
}
}

Expand Down Expand Up @@ -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
}
92 changes: 92 additions & 0 deletions prometheus/internal/difflib.go
Expand Up @@ -22,6 +22,7 @@ import (
"bytes"
"fmt"
"io"
"math"
"strings"
)

Expand All @@ -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
Expand Down

0 comments on commit b481d09

Please sign in to comment.