Skip to content

Commit a5c44f3

Browse files
mpxgriesemer
authored andcommitted
math: add RoundToEven function
Rounding ties to even is statistically useful for some applications. This implementation completes IEEE float64 rounding mode support (in addition to Round, Ceil, Floor, Trunc). This function avoids subtle faults found in ad-hoc implementations, and is simple enough to be inlined by the compiler. Fixes #21748 Change-Id: I09415df2e42435f9e7dabe3bdc0148e9b9ebd609 Reviewed-on: https://go-review.googlesource.com/61211 Reviewed-by: Robert Griesemer <[email protected]> Run-TryBot: Robert Griesemer <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent fcd3288 commit a5c44f3

File tree

3 files changed

+81
-8
lines changed

3 files changed

+81
-8
lines changed

src/math/all_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1774,9 +1774,26 @@ var vfroundSC = [][2]float64{
17741774
{0.5, 1},
17751775
{0.5000000000000001, 1}, // 0.5+epsilon
17761776
{-1.5, -2},
1777+
{-2.5, -3},
17771778
{NaN(), NaN()},
17781779
{Inf(1), Inf(1)},
17791780
{2251799813685249.5, 2251799813685250}, // 1 bit fraction
1781+
{2251799813685250.5, 2251799813685251},
1782+
{4503599627370495.5, 4503599627370496}, // 1 bit fraction, rounding to 0 bit fraction
1783+
{4503599627370497, 4503599627370497}, // large integer
1784+
}
1785+
var vfroundEvenSC = [][2]float64{
1786+
{0, 0},
1787+
{1.390671161567e-309, 0}, // denormal
1788+
{0.49999999999999994, 0}, // 0.5-epsilon
1789+
{0.5, 0},
1790+
{0.5000000000000001, 1}, // 0.5+epsilon
1791+
{-1.5, -2},
1792+
{-2.5, -2},
1793+
{NaN(), NaN()},
1794+
{Inf(1), Inf(1)},
1795+
{2251799813685249.5, 2251799813685250}, // 1 bit fraction
1796+
{2251799813685250.5, 2251799813685250},
17801797
{4503599627370495.5, 4503599627370496}, // 1 bit fraction, rounding to 0 bit fraction
17811798
{4503599627370497, 4503599627370497}, // large integer
17821799
}
@@ -2752,6 +2769,19 @@ func TestRound(t *testing.T) {
27522769
}
27532770
}
27542771

2772+
func TestRoundToEven(t *testing.T) {
2773+
for i := 0; i < len(vf); i++ {
2774+
if f := RoundToEven(vf[i]); !alike(round[i], f) {
2775+
t.Errorf("RoundToEven(%g) = %g, want %g", vf[i], f, round[i])
2776+
}
2777+
}
2778+
for i := 0; i < len(vfroundEvenSC); i++ {
2779+
if f := RoundToEven(vfroundEvenSC[i][0]); !alike(vfroundEvenSC[i][1], f) {
2780+
t.Errorf("RoundToEven(%g) = %g, want %g", vfroundEvenSC[i][0], f, vfroundEvenSC[i][1])
2781+
}
2782+
}
2783+
}
2784+
27552785
func TestSignbit(t *testing.T) {
27562786
for i := 0; i < len(vf); i++ {
27572787
if f := Signbit(vf[i]); signbit[i] != f {
@@ -3413,6 +3443,14 @@ func BenchmarkRound(b *testing.B) {
34133443
GlobalF = x
34143444
}
34153445

3446+
func BenchmarkRoundToEven(b *testing.B) {
3447+
x := 0.0
3448+
for i := 0; i < b.N; i++ {
3449+
x = RoundToEven(roundNeg)
3450+
}
3451+
GlobalF = x
3452+
}
3453+
34163454
func BenchmarkRemainder(b *testing.B) {
34173455
x := 0.0
34183456
for i := 0; i < b.N; i++ {

src/math/bits.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@ const (
88
uvnan = 0x7FF8000000000001
99
uvinf = 0x7FF0000000000000
1010
uvneginf = 0xFFF0000000000000
11+
uvone = 0x3FF0000000000000
1112
mask = 0x7FF
1213
shift = 64 - 11 - 1
1314
bias = 1023
15+
signMask = 1 << 63
16+
fracMask = 1<<shift - 1
1417
)
1518

1619
// Inf returns positive infinity if sign >= 0, negative infinity if sign < 0.

src/math/floor.go

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,29 +71,61 @@ func Round(x float64) float64 {
7171
// }
7272
// return t
7373
// }
74-
const (
75-
signMask = 1 << 63
76-
fracMask = 1<<shift - 1
77-
half = 1 << (shift - 1)
78-
one = bias << shift
79-
)
80-
8174
bits := Float64bits(x)
8275
e := uint(bits>>shift) & mask
8376
if e < bias {
8477
// Round abs(x) < 1 including denormals.
8578
bits &= signMask // +-0
8679
if e == bias-1 {
87-
bits |= one // +-1
80+
bits |= uvone // +-1
8881
}
8982
} else if e < bias+shift {
9083
// Round any abs(x) >= 1 containing a fractional component [0,1).
9184
//
9285
// Numbers with larger exponents are returned unchanged since they
9386
// must be either an integer, infinity, or NaN.
87+
const half = 1 << (shift - 1)
9488
e -= bias
9589
bits += half >> e
9690
bits &^= fracMask >> e
9791
}
9892
return Float64frombits(bits)
9993
}
94+
95+
// RoundToEven returns the nearest integer, rounding ties to even.
96+
//
97+
// Special cases are:
98+
// RoundToEven(±0) = ±0
99+
// RoundToEven(±Inf) = ±Inf
100+
// RoundToEven(NaN) = NaN
101+
func RoundToEven(x float64) float64 {
102+
// RoundToEven is a faster implementation of:
103+
//
104+
// func RoundToEven(x float64) float64 {
105+
// t := math.Trunc(x)
106+
// odd := math.Remainder(t, 2) != 0
107+
// if d := math.Abs(x - t); d > 0.5 || (d == 0.5 && odd) {
108+
// return t + math.Copysign(1, x)
109+
// }
110+
// return t
111+
// }
112+
bits := Float64bits(x)
113+
e := uint(bits>>shift) & mask
114+
if e >= bias {
115+
// Round abs(x) >= 1.
116+
// - Large numbers without fractional components, infinity, and NaN are unchanged.
117+
// - Add 0.499.. or 0.5 before truncating depending on whether the truncated
118+
// number is even or odd (respectively).
119+
const halfMinusULP = (1 << (shift - 1)) - 1
120+
e -= bias
121+
bits += (halfMinusULP + (bits>>(shift-e))&1) >> e
122+
bits &^= fracMask >> e
123+
} else if e == bias-1 && bits&fracMask != 0 {
124+
// Round 0.5 < abs(x) < 1.
125+
bits = bits&signMask | uvone // +-1
126+
} else {
127+
// Round abs(x) <= 0.5 including denormals.
128+
bits &= signMask // +-0
129+
}
130+
return Float64frombits(bits)
131+
}

0 commit comments

Comments
 (0)