Browse Source

rate: fix rounding error in tokensFromDuration

tokensFromDuration performs a unit conversion by multiplying the limit
by the duration in seconds.

Make tokensFromDuration perform conversion by unit-wise multiplication
of limit by seconds and nanoseconds, instead of the conversion to
time.(Duration).Seconds that truncated the nanoseconds.

Fixes golang/go#34861

Change-Id: I94d290d3e32a87541d1702f2b58180270e1a9e96
Reviewed-on: https://go-review.googlesource.com/c/time/+/200900
Run-TryBot: Emmanuel Odeke <emm.odeke@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Emmanuel Odeke <emm.odeke@gmail.com>
Eric Lagergren 6 years ago
parent
commit
6d3f0bb11b
2 changed files with 14 additions and 1 deletions
  1. 5 1
      rate/rate.go
  2. 9 0
      rate/rate_test.go

+ 5 - 1
rate/rate.go

@@ -387,5 +387,9 @@ func (limit Limit) durationFromTokens(tokens float64) time.Duration {
 // tokensFromDuration is a unit conversion function from a time duration to the number of tokens
 // which could be accumulated during that duration at a rate of limit tokens per second.
 func (limit Limit) tokensFromDuration(d time.Duration) float64 {
-	return d.Seconds() * float64(limit)
+	// Split the integer and fractional parts ourself to minimize rounding errors.
+	// See golang.org/issues/34861.
+	sec := float64(d/time.Second) * float64(limit)
+	nsec := float64(d%time.Second) * float64(limit)
+	return sec + nsec/1e9
 }

+ 9 - 0
rate/rate_test.go

@@ -131,6 +131,15 @@ func TestLimiterJumpBackwards(t *testing.T) {
 	})
 }
 
+// Ensure that tokensFromDuration doesn't produce
+// rounding errors by truncating nanoseconds.
+// See golang.org/issues/34861.
+func TestLimiter_noTruncationErrors(t *testing.T) {
+	if !NewLimiter(0.7692307692307693, 1).Allow() {
+		t.Fatal("expected true")
+	}
+}
+
 func TestSimultaneousRequests(t *testing.T) {
 	const (
 		limit       = 1