Commit 105cc2bd authored by Russ Cox's avatar Russ Cox

time: test and fix Time.Round, Duration.Round for d > 2⁶²

Round uses r+r < d to decide whether the remainder is
above or below half of d (to decide whether to round up or down).
This is wrong when r+r wraps negative, because it looks < d
but is really > d.

No one will ever care about rounding to a multiple of
d > 2⁶² (about 146 years), but might as well get it right.

Fixes #19807.

Change-Id: I1b55a742dc36e02a7465bc778bf5dd74fe71f7c0
Reviewed-on: https://go-review.googlesource.com/39151
Run-TryBot: Russ Cox <rsc@golang.org>
Reviewed-by: 's avatarRob Pike <r@golang.org>
Reviewed-by: 's avatarDavid Chase <drchase@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
parent 8ab71304
...@@ -798,6 +798,12 @@ func (d Duration) Truncate(m Duration) Duration { ...@@ -798,6 +798,12 @@ func (d Duration) Truncate(m Duration) Duration {
return d - d%m return d - d%m
} }
// lessThanHalf reports whether x+x < y but avoids overflow,
// assuming x and y are both positive (Duration is signed).
func lessThanHalf(x, y Duration) bool {
return uint64(x)+uint64(x) < uint64(y)
}
// Round returns the result of rounding d to the nearest multiple of m. // Round returns the result of rounding d to the nearest multiple of m.
// The rounding behavior for halfway values is to round away from zero. // The rounding behavior for halfway values is to round away from zero.
// If the result exceeds the maximum (or minimum) // If the result exceeds the maximum (or minimum)
...@@ -811,7 +817,7 @@ func (d Duration) Round(m Duration) Duration { ...@@ -811,7 +817,7 @@ func (d Duration) Round(m Duration) Duration {
r := d % m r := d % m
if d < 0 { if d < 0 {
r = -r r = -r
if r+r < m { if lessThanHalf(r, m) {
return d + r return d + r
} }
if d1 := d - m + r; d1 < d { if d1 := d - m + r; d1 < d {
...@@ -819,7 +825,7 @@ func (d Duration) Round(m Duration) Duration { ...@@ -819,7 +825,7 @@ func (d Duration) Round(m Duration) Duration {
} }
return minDuration // overflow return minDuration // overflow
} }
if r+r < m { if lessThanHalf(r, m) {
return d - r return d - r
} }
if d1 := d + m - r; d1 > d { if d1 := d + m - r; d1 > d {
...@@ -1400,7 +1406,7 @@ func (t Time) Round(d Duration) Time { ...@@ -1400,7 +1406,7 @@ func (t Time) Round(d Duration) Time {
return t return t
} }
_, r := div(t, d) _, r := div(t, d)
if r+r < d { if lessThanHalf(r, d) {
return t.Add(-r) return t.Add(-r)
} }
return t.Add(d - r) return t.Add(d - r)
......
...@@ -233,6 +233,7 @@ var truncateRoundTests = []struct { ...@@ -233,6 +233,7 @@ var truncateRoundTests = []struct {
{Date(-1, January, 1, 12, 15, 31, 5e8, UTC), 3}, {Date(-1, January, 1, 12, 15, 31, 5e8, UTC), 3},
{Date(2012, January, 1, 12, 15, 30, 5e8, UTC), Second}, {Date(2012, January, 1, 12, 15, 30, 5e8, UTC), Second},
{Date(2012, January, 1, 12, 15, 31, 5e8, UTC), Second}, {Date(2012, January, 1, 12, 15, 31, 5e8, UTC), Second},
{Unix(-19012425939, 649146258), 7435029458905025217}, // 5.8*d rounds to 6*d, but .8*d+.8*d < 0 < d
} }
func TestTruncateRound(t *testing.T) { func TestTruncateRound(t *testing.T) {
...@@ -1107,6 +1108,7 @@ var durationRoundTests = []struct { ...@@ -1107,6 +1108,7 @@ var durationRoundTests = []struct {
{9e18, 5e18, 1<<63 - 1}, {9e18, 5e18, 1<<63 - 1},
{-8e18, 3e18, -9e18}, {-8e18, 3e18, -9e18},
{-9e18, 5e18, -1 << 63}, {-9e18, 5e18, -1 << 63},
{3<<61 - 1, 3 << 61, 3 << 61},
} }
func TestDurationRound(t *testing.T) { func TestDurationRound(t *testing.T) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment