diff --git a/util/almost/almost.go b/util/almost/almost.go index a40462658..5f866b89b 100644 --- a/util/almost/almost.go +++ b/util/almost/almost.go @@ -22,10 +22,10 @@ import ( var minNormal = math.Float64frombits(0x0010000000000000) // The smallest positive normal value of type float64. // Equal returns true if a and b differ by less than their sum -// multiplied by epsilon. +// multiplied by epsilon, or if both are StaleNaN, or if both are any other NaN. func Equal(a, b, epsilon float64) bool { - // StaleNaN is a special value that is used as staleness maker, so - // the two values are equal when both are exactly equals to stale NaN. + // StaleNaN is a special value that is used as staleness maker, and + // we don't want it to compare equal to any other NaN. if value.IsStaleNaN(a) || value.IsStaleNaN(b) { return value.IsStaleNaN(a) && value.IsStaleNaN(b) } diff --git a/util/almost/almost_test.go b/util/almost/almost_test.go new file mode 100644 index 000000000..fba37f13f --- /dev/null +++ b/util/almost/almost_test.go @@ -0,0 +1,50 @@ +// Copyright 2024 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package almost + +import ( + "fmt" + "math" + "testing" + + "github.com/prometheus/prometheus/model/value" +) + +func TestEqual(t *testing.T) { + staleNaN := math.Float64frombits(value.StaleNaN) + tests := []struct { + a float64 + b float64 + epsilon float64 + want bool + }{ + {0.0, 0.0, 0.0, true}, + {0.0, 0.1, 0.0, false}, + {1.0, 1.1, 0.1, true}, + {-1.0, -1.1, 0.1, true}, + {math.MaxFloat64, math.MaxFloat64 / 10, 0.1, false}, + {1.0, math.NaN(), 0.1, false}, + {math.NaN(), math.NaN(), 0.1, true}, + {math.NaN(), staleNaN, 0.1, false}, + {staleNaN, math.NaN(), 0.1, false}, + {staleNaN, staleNaN, 0.1, true}, + } + for _, tt := range tests { + t.Run(fmt.Sprintf("%v,%v,%v", tt.a, tt.b, tt.epsilon), func(t *testing.T) { + if got := Equal(tt.a, tt.b, tt.epsilon); got != tt.want { + t.Errorf("Equal(%v,%v,%v) = %v, want %v", tt.a, tt.b, tt.epsilon, got, tt.want) + } + }) + } +}