Skip to content

Commit 3348fd0

Browse files
smasher164gopherbot
authored andcommitted
math: add Compare and Compare32
This change introduces the Compare and Compare32 functions based on the total-ordering predicate in IEEE-754, section 5.10. In particular, * -NaN is ordered before any other value * +NaN is ordered after any other value * -0 is ordered before +0 * All other values are ordered the usual way Compare-8 0.4537n ± 1% Compare32-8 0.3752n ± 1% geomean 0.4126n Fixes #56491. Change-Id: I5c9c77430a2872f380688c1b0a66f2105b77d5ac Reviewed-on: https://go-review.googlesource.com/c/go/+/467515 Reviewed-by: WANG Xuerui <[email protected]> Run-TryBot: Ian Lance Taylor <[email protected]> Reviewed-by: Lynn Boger <[email protected]> Auto-Submit: Ian Lance Taylor <[email protected]> Run-TryBot: Ian Lance Taylor <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> Reviewed-by: Michael Pratt <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent 40ed359 commit 3348fd0

File tree

3 files changed

+149
-0
lines changed

3 files changed

+149
-0
lines changed

api/next/56491.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pkg math, func Compare(float64, float64) int #56491
2+
pkg math, func Compare32(float32, float32) int #56491

src/math/all_test.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2094,6 +2094,67 @@ var sqrt32 = []float32{
20942094
-5.0106036182710749e+00,
20952095
}
20962096

2097+
type compareTest[F float32 | float64] struct {
2098+
x, y F
2099+
want int
2100+
}
2101+
2102+
func compareCasesFloat64() []compareTest[float64] {
2103+
zero, nan, inf := 0.0, NaN(), Inf(0)
2104+
2105+
// construct -NaN manually from its bit representation,
2106+
// since IEEE doesn't mandate negate(NaN) change the sign bit
2107+
unegnan := Float64bits(nan)
2108+
unegnan ^= 1 << 63
2109+
negnan := Float64frombits(unegnan)
2110+
return []compareTest[float64]{
2111+
{negnan, -inf, -1},
2112+
{-inf, negnan, 1},
2113+
{-inf, -Pi, -1},
2114+
{-Pi, -inf, 1},
2115+
{-Pi, -zero, -1},
2116+
{-zero, -Pi, 1},
2117+
{-zero, 0, -1},
2118+
{0, -zero, 1},
2119+
{0, Pi, -1},
2120+
{Pi, 0, 1},
2121+
{Pi, inf, -1},
2122+
{inf, Pi, 1},
2123+
{inf, nan, -1},
2124+
{nan, inf, 1},
2125+
{Pi, Pi, 0},
2126+
{negnan, negnan, 0},
2127+
}
2128+
}
2129+
2130+
func compareCasesFloat32() []compareTest[float32] {
2131+
zero, nan, inf := float32(0.0), float32(NaN()), float32(Inf(0))
2132+
2133+
// construct -NaN manually from its bit representation,
2134+
// since IEEE doesn't mandate negate(NaN) change the sign bit
2135+
unegnan := Float32bits(nan)
2136+
unegnan ^= 1 << 31
2137+
negnan := Float32frombits(unegnan)
2138+
return []compareTest[float32]{
2139+
{negnan, -inf, -1},
2140+
{-inf, negnan, 1},
2141+
{-inf, -Pi, -1},
2142+
{-Pi, -inf, 1},
2143+
{-Pi, -zero, -1},
2144+
{-zero, -Pi, 1},
2145+
{-zero, 0, -1},
2146+
{0, -zero, 1},
2147+
{0, Pi, -1},
2148+
{Pi, 0, 1},
2149+
{Pi, inf, -1},
2150+
{inf, Pi, 1},
2151+
{inf, nan, -1},
2152+
{nan, inf, 1},
2153+
{Pi, Pi, 0},
2154+
{negnan, negnan, 0},
2155+
}
2156+
}
2157+
20972158
func tolerance(a, b, e float64) bool {
20982159
// Multiplying by e here can underflow denormal values to zero.
20992160
// Check a==b so that at least if a and b are small and identical
@@ -2261,6 +2322,22 @@ func TestCeil(t *testing.T) {
22612322
}
22622323
}
22632324

2325+
func TestCompare(t *testing.T) {
2326+
// -NaN < -∞ < -3.14 < -0 < 0 < 3.14 < ∞ < NaN
2327+
for _, c := range compareCasesFloat64() {
2328+
cmp := Compare(c.x, c.y)
2329+
if cmp != c.want {
2330+
t.Errorf("Compare(%v, %v) = %d, want %v", c.x, c.y, cmp, c.want)
2331+
}
2332+
}
2333+
for _, c := range compareCasesFloat32() {
2334+
cmp := Compare32(c.x, c.y)
2335+
if cmp != c.want {
2336+
t.Errorf("Compare32(%v, %v) = %d, want %v", c.x, c.y, cmp, c.want)
2337+
}
2338+
}
2339+
}
2340+
22642341
func TestCopysign(t *testing.T) {
22652342
for i := 0; i < len(vf); i++ {
22662343
if f := Copysign(vf[i], -1); copysign[i] != f {
@@ -3320,6 +3397,23 @@ func BenchmarkCeil(b *testing.B) {
33203397
GlobalF = x
33213398
}
33223399

3400+
func BenchmarkCompare(b *testing.B) {
3401+
x := 0
3402+
for i := 0; i < b.N; i++ {
3403+
x = Compare(GlobalF, 1.5)
3404+
}
3405+
GlobalI = x
3406+
}
3407+
3408+
func BenchmarkCompare32(b *testing.B) {
3409+
x := 0
3410+
globalF32 := float32(GlobalF)
3411+
for i := 0; i < b.N; i++ {
3412+
x = Compare32(globalF32, 1.5)
3413+
}
3414+
GlobalI = x
3415+
}
3416+
33233417
var copysignNeg = -1.0
33243418

33253419
func BenchmarkCopysign(b *testing.B) {

src/math/compare.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package math
6+
7+
func sign[I int32 | int64](a, b I) int {
8+
if a < b {
9+
return -1
10+
}
11+
if a > b {
12+
return 1
13+
}
14+
return 0
15+
}
16+
17+
// Compare compares a and b such that
18+
// -NaN is ordered before any other value,
19+
// +NaN is ordered after any other value,
20+
// and -0 is ordered before +0.
21+
// In other words, it defines a total order over floats
22+
// (according to the total-ordering predicate in IEEE-754, section 5.10).
23+
// It returns 0 if a == b, -1 if a < b, and +1 if a > b.
24+
func Compare(a, b float64) int {
25+
// Perform a bitwise comparison (a < b) by casting the float64s into an int64s.
26+
x := int64(Float64bits(a))
27+
y := int64(Float64bits(b))
28+
29+
// If a and b are both negative, flip the comparison so that we check a > b.
30+
if x < 0 && y < 0 {
31+
return sign(y, x)
32+
}
33+
return sign(x, y)
34+
}
35+
36+
// Compare32 compares a and b such that
37+
// -NaN is ordered before any other value,
38+
// +NaN is ordered after any other value,
39+
// and -0 is ordered before +0.
40+
// In other words, it defines a total order over floats
41+
// (according to the total-ordering predicate in IEEE-754, section 5.10).
42+
// It returns 0 if a == b, -1 if a < b, and +1 if a > b.
43+
func Compare32(a, b float32) int {
44+
// Perform a bitwise comparison (a < b) by casting the float32s into an int32s.
45+
x := int32(Float32bits(a))
46+
y := int32(Float32bits(b))
47+
48+
// If a and b are both negative, flip the comparison so that we check a > b.
49+
if x < 0 && y < 0 {
50+
return sign(y, x)
51+
}
52+
return sign(x, y)
53+
}

0 commit comments

Comments
 (0)