-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
logictest: compare floating point values approximately on s390x
On s390x in the std math package and some c-deps, floating point calculations can produce results that differ from the values calculated on amd64. This patch adds a function to compare logictest floating point and decimal values within a small relative margin on s390x. Release note: None
- Loading branch information
1 parent
aa4d1f0
commit b3237ed
Showing
7 changed files
with
302 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") | ||
|
||
go_library( | ||
name = "floatcmp", | ||
srcs = ["floatcmp.go"], | ||
importpath = "github.com/cockroachdb/cockroach/pkg/testutils/floatcmp", | ||
visibility = ["//visibility:public"], | ||
deps = [ | ||
"@com_github_google_go_cmp//cmp", | ||
"@com_github_google_go_cmp//cmp/cmpopts", | ||
], | ||
) | ||
|
||
go_test( | ||
name = "floatcmp_test", | ||
size = "small", | ||
srcs = ["floatcmp_test.go"], | ||
embed = [":floatcmp"], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
// Copyright 2021 The Cockroach Authors. | ||
// | ||
// Use of this software is governed by the Business Source License | ||
// included in the file licenses/BSL.txt. | ||
// | ||
// As of the Change Date specified in that file, in accordance with | ||
// the Business Source License, use of this software will be governed | ||
// by the Apache License, Version 2.0, included in the file | ||
// licenses/APL.txt. | ||
|
||
// Package floatcmp provides functions for determining float values to be equal | ||
// if they are within a tolerance. It is designed to be used in tests. | ||
package floatcmp | ||
|
||
import ( | ||
"github.com/google/go-cmp/cmp" | ||
"github.com/google/go-cmp/cmp/cmpopts" | ||
) | ||
|
||
const ( | ||
// CloseFraction can be used to set a "close" tolerance for the fraction | ||
// argument of functions in this package. It should typically be used with | ||
// the CloseMargin constant for the margin argument. Its value is taken from | ||
// the close tolerances in go's math package. | ||
CloseFraction float64 = 1e-14 | ||
|
||
// CloseMargin can be used to set a "close" tolerance for the margin | ||
// argument of functions in this package. It should typically be used with | ||
// the CloseFraction constant for the fraction argument. | ||
// | ||
// It is set to the square of CloseFraction so it is only used when the | ||
// smaller of the absolute expected and actual values is in the range: | ||
// | ||
// -CloseFraction <= 0 <= CloseFraction | ||
// | ||
// CloseMargin is greater than 0 otherwise if either expected or actual were | ||
// 0 the calculated tolerance from the fraction would be 0. | ||
CloseMargin float64 = CloseFraction * CloseFraction | ||
) | ||
|
||
// EqualApprox reports whether expected and actual are deeply equal with the | ||
// following modifications for float64 and float32 types: | ||
// | ||
// • If both expected and actual are not NaN or infinate, they are equal within | ||
// the larger of the relative fraction or absolute margin calculated from the | ||
// fraction and margin arguments. | ||
// | ||
// • If both expected and actual are NaN, they are equal. | ||
// | ||
// Both fraction and margin must be non-negative. | ||
// | ||
// fraction is used to calculate the tolerance as a relative fraction of the | ||
// smaller of expected and actual: | ||
// | ||
// tolerance_frac = (fraction * min(|expected|, |actual|)) | ||
// | ||
// margin specifies the tolerance as an absolute value: | ||
// | ||
// tolerance_marg = margin | ||
// | ||
// The tolerance used to determine approximate equality is: | ||
// | ||
// tolerance = max(tolerance_frac, tolerance_marg) | ||
// | ||
// To use only one of fraction or margin, set the other to 0. | ||
// | ||
// For comparing expected and actual values in tests, typically the fraction | ||
// should be set to the smallest relative fraction to tolerate. The margin | ||
// should be set to a much smaller value so that it is only used when: | ||
// | ||
// (fraction * min(|expected|, |actual|)) < margin | ||
// | ||
// which allows expected and actual to be approximately equal within margin when | ||
// either is 0. | ||
func EqualApprox(expected interface{}, actual interface{}, fraction float64, margin float64) bool { | ||
return cmp.Equal(expected, actual, cmpopts.EquateApprox(fraction, margin), cmpopts.EquateNaNs()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
// Copyright 2021 The Cockroach Authors. | ||
// | ||
// Use of this software is governed by the Business Source License | ||
// included in the file licenses/BSL.txt. | ||
// | ||
// As of the Change Date specified in that file, in accordance with | ||
// the Business Source License, use of this software will be governed | ||
// by the Apache License, Version 2.0, included in the file | ||
// licenses/APL.txt. | ||
|
||
package floatcmp | ||
|
||
import ( | ||
"math" | ||
"testing" | ||
) | ||
|
||
type ( | ||
// floatArgs holds the expected and actual values of floating point equality tests. | ||
floatArgs struct { | ||
expected float64 | ||
actual float64 | ||
} | ||
|
||
// testStruct structs are the values compared in struct equality tests. | ||
testStruct struct { | ||
X, Y float64 | ||
I int | ||
} | ||
|
||
// structArgs holds the expected and actual values of struct equality tests. | ||
structArgs struct { | ||
expected testStruct | ||
actual testStruct | ||
} | ||
|
||
// floatTestCase represents a test case for floating point values. | ||
floatTestCase struct { | ||
name string | ||
args floatArgs | ||
want bool | ||
} | ||
|
||
// structTestCase represents a test case for struct values. | ||
structTestCase struct { | ||
name string | ||
args structArgs | ||
want bool | ||
} | ||
) | ||
|
||
var floatTests = []floatTestCase{ | ||
{ | ||
name: "zeros", | ||
args: floatArgs{expected: 0, actual: 0}, | ||
want: true, | ||
}, | ||
{ | ||
name: "NaNs", | ||
args: floatArgs{expected: math.NaN(), actual: math.NaN()}, | ||
want: true, | ||
}, | ||
{ | ||
name: "zero not close to NaN", | ||
args: floatArgs{expected: 0, actual: math.NaN()}, | ||
want: false, | ||
}, | ||
{ | ||
name: "positive infinities", | ||
args: floatArgs{expected: math.Inf(+1), actual: math.Inf(+1)}, | ||
want: true, | ||
}, | ||
{ | ||
name: "negative infinities", | ||
args: floatArgs{expected: math.Inf(-1), actual: math.Inf(-1)}, | ||
want: true, | ||
}, | ||
{ | ||
name: "ones", | ||
args: floatArgs{expected: 1, actual: 1}, | ||
want: true, | ||
}, | ||
{ | ||
name: "signs", | ||
args: floatArgs{expected: 1, actual: -1}, | ||
want: false, | ||
}, | ||
{ | ||
name: "different", | ||
args: floatArgs{expected: 1, actual: 2}, | ||
want: false, | ||
}, | ||
{ | ||
name: "close to zero", | ||
args: floatArgs{expected: 0, actual: math.Nextafter(0+CloseMargin, math.Inf(-1))}, | ||
want: true, | ||
}, | ||
{ | ||
name: "not close to zero", | ||
args: floatArgs{expected: 0, actual: math.Nextafter(0+CloseMargin, math.Inf(+1))}, | ||
want: false, | ||
}, | ||
{ | ||
name: "close to CloseFraction", | ||
args: floatArgs{expected: CloseFraction, actual: math.Nextafter(CloseFraction+CloseMargin, math.Inf(-1))}, | ||
want: true, | ||
}, | ||
{ | ||
name: "not close to CloseFraction", | ||
args: floatArgs{expected: CloseFraction, actual: math.Nextafter(CloseFraction+CloseMargin, math.Inf(+1))}, | ||
want: false, | ||
}, | ||
{ | ||
name: "close to one", | ||
args: floatArgs{expected: 1, actual: math.Nextafter(1+1*CloseFraction, math.Inf(-1))}, | ||
want: true, | ||
}, | ||
{ | ||
name: "not close to one", | ||
args: floatArgs{expected: 1, actual: math.Nextafter(1+1*CloseFraction, math.Inf(+1))}, | ||
want: false, | ||
}, | ||
} | ||
|
||
// toStructTests transforms an array of floatTestsCases into an array of structTestCases by | ||
// copying the expected and actual values of each floatTestCase into corresponding values | ||
// in each structTestCase. | ||
func toStructTests(floatTestCases []floatTestCase) []structTestCase { | ||
structTestCases := make([]structTestCase, 0, len(floatTestCases)) | ||
for _, ft := range floatTestCases { | ||
structTestCases = append(structTestCases, | ||
structTestCase{ | ||
name: ft.name + " struct", | ||
args: structArgs{expected: testStruct{X: ft.args.expected, Y: ft.args.expected, I: 0}, actual: testStruct{X: ft.args.actual, Y: ft.args.actual, I: 0}}, | ||
want: ft.want, | ||
}) | ||
} | ||
return structTestCases | ||
} | ||
|
||
func TestEqualClose(t *testing.T) { | ||
for _, tt := range floatTests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
if got := EqualApprox(tt.args.expected, tt.args.actual, CloseFraction, CloseMargin); got != tt.want { | ||
t.Errorf("Close(%.16e, %.16e) = %v, want %v", tt.args.expected, tt.args.actual, got, tt.want) | ||
} | ||
}) | ||
} | ||
for _, tt := range toStructTests(floatTests) { | ||
t.Run(tt.name, func(t *testing.T) { | ||
if got := EqualApprox(tt.args.expected, tt.args.actual, CloseFraction, CloseMargin); got != tt.want { | ||
t.Errorf("Close(%.v, %.v) = %v, want %v", tt.args.expected, tt.args.actual, got, tt.want) | ||
} | ||
}) | ||
} | ||
} |