From e76e21e4c7e80bc09deba3e2082b325f977b9275 Mon Sep 17 00:00:00 2001 From: Aliaksandr Valialkin Date: Thu, 31 Oct 2019 13:24:47 +0200 Subject: [PATCH] lib/decimal: speed up FromFloat for common case with integers --- lib/decimal/decimal.go | 94 ++++++++++++++++++++----------------- lib/decimal/decimal_test.go | 4 +- 2 files changed, 54 insertions(+), 44 deletions(-) diff --git a/lib/decimal/decimal.go b/lib/decimal/decimal.go index 7bb739a98..8b02be0c8 100644 --- a/lib/decimal/decimal.go +++ b/lib/decimal/decimal.go @@ -265,64 +265,74 @@ var ( // For instance, for f = -1.234 it returns v = -1234, e = -3. // // FromFloat doesn't work properly with NaN values, so don't pass them here. -func FromFloat(f float64) (v int64, e int16) { - if math.IsInf(f, 0) { - // Special case for Inf - if math.IsInf(f, 1) { - return vInfPos, 0 - } - return vInfNeg, 0 - } - - minus := false - if f < 0 { - f = -f - minus = true - } +func FromFloat(f float64) (int64, int16) { if f == 0 { - // Special case for 0.0 and -0.0 return 0, 0 } - v, e = positiveFloatToDecimal(f) - if minus { - v = -v + if math.IsInf(f, 0) { + return fromFloatInf(f) } - if v == 0 { - e = 0 - } else if v > vMax { - v = vMax - } else if v < vMin { + if f > 0 { + v, e := positiveFloatToDecimal(f) + if v > vMax { + v = vMax + } + return v, e + } + v, e := positiveFloatToDecimal(-f) + v = -v + if v < vMin { v = vMin } return v, e } +func fromFloatInf(f float64) (int64, int16) { + // Special case for Inf + if math.IsInf(f, 1) { + return vInfPos, 0 + } + return vInfNeg, 0 +} + func positiveFloatToDecimal(f float64) (int64, int16) { - var scale int16 + // There is no need in checking for f == 0, since it should be already checked by the caller. u := uint64(f) - if float64(u) == f { - // Fast path for integers. - for u >= 1<<55 { - // Remove trailing garbage bits left after float64->uint64 conversion, - // since float64 contains only 53 significant bits. - // See https://en.wikipedia.org/wiki/Double-precision_floating-point_format - u /= 10 - scale++ - } - if u%10 != 0 || u == 0 { - return int64(u), scale - } - // Minimize v by converting trailing zeros to scale. + if float64(u) != f { + return positiveFloatToDecimalSlow(f) + } + // Fast path for integers. + if u < 1<<55 && u%10 != 0 { + return int64(u), 0 + } + return getDecimalAndScale(u) +} + +func getDecimalAndScale(u uint64) (int64, int16) { + var scale int16 + for u >= 1<<55 { + // Remove trailing garbage bits left after float64->uint64 conversion, + // since float64 contains only 53 significant bits. + // See https://en.wikipedia.org/wiki/Double-precision_floating-point_format u /= 10 scale++ - for u != 0 && u%10 == 0 { - u /= 10 - scale++ - } + } + if u%10 != 0 { return int64(u), scale } + // Minimize v by converting trailing zeros to scale. + u /= 10 + scale++ + for u != 0 && u%10 == 0 { + u /= 10 + scale++ + } + return int64(u), scale +} +func positiveFloatToDecimalSlow(f float64) (int64, int16) { // Slow path for floating point numbers. + var scale int16 prec := conversionPrecision if f > 1e6 || f < 1e-6 { // Normalize f, so it is in the small range suitable @@ -352,7 +362,7 @@ func positiveFloatToDecimal(f float64) (int64, int16) { f *= 100 scale -= 2 } - u = uint64(f) + u := uint64(f) if u%10 != 0 { return int64(u), scale } diff --git a/lib/decimal/decimal_test.go b/lib/decimal/decimal_test.go index ffb9d95b4..2f2cc53e6 100644 --- a/lib/decimal/decimal_test.go +++ b/lib/decimal/decimal_test.go @@ -18,7 +18,7 @@ func TestPositiveFloatToDecimal(t *testing.T) { t.Fatalf("unexpected exponent for positiveFloatToDecimal(%f); got %d; want %d", f, exponent, exponentExpected) } } - f(0, 0, 0) + f(0, 0, 1) // The exponent is 1 is OK here. See comment in positiveFloatToDecimal. f(1, 1, 0) f(30, 3, 1) f(12345678900000000, 123456789, 8) @@ -206,7 +206,7 @@ func TestAppendFloatToDecimal(t *testing.T) { // no-op testAppendFloatToDecimal(t, []float64{}, nil, 0) testAppendFloatToDecimal(t, []float64{0}, []int64{0}, 0) - testAppendFloatToDecimal(t, []float64{0, 1, -1, 12345678, -123456789}, []int64{0, 1, -1, 12345678, -123456789}, 0) + testAppendFloatToDecimal(t, []float64{0, -0, 1, -1, 12345678, -123456789}, []int64{0, 0, 1, -1, 12345678, -123456789}, 0) // upExp testAppendFloatToDecimal(t, []float64{-24, 0, 4.123, 0.3}, []int64{-24000, 0, 4123, 300}, -3)