From f790d55c4fe2b9ece15b39b63a6352e89b137b62 Mon Sep 17 00:00:00 2001 From: Caleb Spare Date: Sat, 12 Jan 2019 17:13:47 -0800 Subject: [PATCH] Use bit twiddling hack for decimalLen64 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This helps a little for small, common integers. name old time/op new time/op delta AppendFloat32/0e+00-12 2.96ns ± 2% 2.96ns ± 1% ~ (p=0.992 n=7+7) AppendFloat32/1e+00-12 13.3ns ± 2% 13.1ns ± 2% -1.82% (p=0.016 n=7+7) AppendFloat32/3e-01-12 32.6ns ± 1% 32.4ns ± 1% ~ (p=0.179 n=7+7) AppendFloat32/1e+06-12 17.6ns ± 1% 16.9ns ± 1% -3.55% (p=0.000 n=7+6) AppendFloat32/-1.2345e+02-12 34.2ns ± 1% 34.1ns ± 1% ~ (p=0.362 n=7+7) AppendFloat64/0e+00-12 3.29ns ± 1% 3.28ns ± 1% ~ (p=0.368 n=7+7) AppendFloat64/1e+00-12 15.2ns ± 2% 14.5ns ± 1% -4.19% (p=0.001 n=7+6) AppendFloat64/3e-01-12 50.8ns ± 0% 49.0ns ± 1% -3.51% (p=0.000 n=6+7) AppendFloat64/1e+06-12 21.0ns ± 1% 21.1ns ± 2% ~ (p=0.400 n=6+7) AppendFloat64/-1.2345e+02-12 50.1ns ± 0% 49.0ns ± 1% -2.06% (p=0.004 n=5+6) AppendFloat64/6.226662346353213e-309-12 39.9ns ± 1% 40.3ns ± 1% +1.07% (p=0.018 n=7+7) --- ryu.go | 7 ++++++ ryu32.go | 1 + ryu64.go | 67 +++++++++++++++++++++-------------------------------- ryu_test.go | 24 +++++++++++++++++++ 4 files changed, 58 insertions(+), 41 deletions(-) diff --git a/ryu.go b/ryu.go index 8c6820b..5f31c34 100644 --- a/ryu.go +++ b/ryu.go @@ -160,6 +160,13 @@ func pow5Bits(e int32) int32 { // FIXME(caleb): Document how these are optimized. +func boolToInt(b bool) int { + if b { + return 1 + } + return 0 +} + func boolToUint32(b bool) uint32 { if b { return 1 diff --git a/ryu32.go b/ryu32.go index 61cf8ec..b0ed9af 100644 --- a/ryu32.go +++ b/ryu32.go @@ -240,6 +240,7 @@ func float32ToDecimal(mant, exp uint32) dec32 { func decimalLen32(u uint32) int { // Function precondition: u is not a 10-digit number. // (9 digits are sufficient for round-tripping.) + // This benchmarked faster than the log2 approach used for uint64s. assert(u < 1000000000, "too big") switch { case u >= 100000000: diff --git a/ryu64.go b/ryu64.go index e7167a6..5c6081a 100644 --- a/ryu64.go +++ b/ryu64.go @@ -292,48 +292,33 @@ func float64ToDecimal(mant, exp uint64) dec64 { return dec64{m: out, e: e10 + removed} } +var powersOf10 = [...]uint64{ + 1e0, + 1e1, + 1e2, + 1e3, + 1e4, + 1e5, + 1e6, + 1e7, + 1e8, + 1e9, + 1e10, + 1e11, + 1e12, + 1e13, + 1e14, + 1e15, + 1e16, + 1e17, + // We only need to find the length of at most 17 digit numbers. +} + func decimalLen64(u uint64) int { - // This is slightly faster than a loop. FIXME: Confirm. - // The average output length is 16.38 digits, so we check high-to-low. - // Function precondition: v is not an 18, 19, or 20-digit number. - // (17 digits are sufficient for round-tripping.) - assert(u < 100000000000000000, "too big") - switch { - case u >= 10000000000000000: - return 17 - case u >= 1000000000000000: - return 16 - case u >= 100000000000000: - return 15 - case u >= 10000000000000: - return 14 - case u >= 1000000000000: - return 13 - case u >= 100000000000: - return 12 - case u >= 10000000000: - return 11 - case u >= 1000000000: - return 10 - case u >= 100000000: - return 9 - case u >= 10000000: - return 8 - case u >= 1000000: - return 7 - case u >= 100000: - return 6 - case u >= 10000: - return 5 - case u >= 1000: - return 4 - case u >= 100: - return 3 - case u >= 10: - return 2 - default: - return 1 - } + // http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog10 + log2 := 64 - bits.LeadingZeros64(u) - 1 + t := (log2 + 1) * 1233 >> 12 + return t - boolToInt(u < powersOf10[t]) + 1 } func mulShift64(m uint64, mul uint128, shift int32) uint64 { diff --git a/ryu_test.go b/ryu_test.go index d7b441d..9732c90 100644 --- a/ryu_test.go +++ b/ryu_test.go @@ -19,6 +19,7 @@ package ryu import ( "math" + "math/big" "math/rand" "strconv" "testing" @@ -219,3 +220,26 @@ func BenchmarkStrconvAppendFloat64(b *testing.B) { }) } } + +func TestDecimalLen(t *testing.T) { + for n := uint64(1); n < 1000; n++ { + testDecimalLen(t, n) + } + for i := 0; i < 1e5; i++ { + n := uint64(rand.Intn(99999999999999999) + 1) + testDecimalLen(t, n) + } +} + +func testDecimalLen(t *testing.T, n uint64) { + t.Helper() + want := len(big.NewInt(int64(n)).String()) // n fits into int64 + if got := decimalLen64(n); got != want { + t.Fatalf("decimalLen64(%d): got %d; want %d", n, got, want) + } + if n < math.MaxUint32 { + if got := decimalLen32(uint32(n)); got != want { + t.Fatalf("decimalLen32(%d): got %d; want %d", n, got, want) + } + } +}