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>
diff --git a/rate/rate.go b/rate/rate.go
index 85c18b5..3cd1498 100644
--- a/rate/rate.go
+++ b/rate/rate.go
@@ -387,5 +387,9 @@
// 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
}
diff --git a/rate/rate_test.go b/rate/rate_test.go
index 448300d..b0ed34d 100644
--- a/rate/rate_test.go
+++ b/rate/rate_test.go
@@ -131,6 +131,15 @@
})
}
+// 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