From 3d1f4408cfb01e12e366abbcb8cbeb7a4402171f Mon Sep 17 00:00:00 2001 From: Aliaksandr Valialkin Date: Wed, 20 Nov 2019 12:00:46 +0200 Subject: [PATCH] lib/decimal: increase decimal->float speed conversion for integer numbers --- lib/decimal/decimal.go | 76 ++++++++++-------------------- lib/decimal/decimal_test.go | 45 +++++++++++++++--- lib/decimal/decimal_timing_test.go | 23 ++++----- 3 files changed, 73 insertions(+), 71 deletions(-) diff --git a/lib/decimal/decimal.go b/lib/decimal/decimal.go index 4df8035aa..73505bba3 100644 --- a/lib/decimal/decimal.go +++ b/lib/decimal/decimal.go @@ -45,10 +45,6 @@ func CalibrateScale(a []int64, ae int16, b []int64, be int16) (e int16) { } if downExp > 0 { for i, v := range b { - if v == vInfPos || v == vInfNeg { - // Special case for these values - do not touch them. - continue - } adjExp := downExp for adjExp > 0 { v /= 10 @@ -83,36 +79,35 @@ func ExtendInt64sCapacity(dst []int64, additionalItems int) []int64 { // AppendDecimalToFloat converts each item in va to f=v*10^e, appends it // to dst and returns the resulting dst. func AppendDecimalToFloat(dst []float64, va []int64, e int16) []float64 { - if fastnum.IsInt64Zeros(va) { - return fastnum.AppendFloat64Zeros(dst, len(va)) - } - if e == 0 && fastnum.IsInt64Ones(va) { - return fastnum.AppendFloat64Ones(dst, len(va)) - } - // Extend dst capacity in order to eliminate memory allocations below. dst = ExtendFloat64sCapacity(dst, len(va)) + if fastnum.IsInt64Zeros(va) { + return fastnum.AppendFloat64Zeros(dst, len(va)) + } + if e == 0 { + if fastnum.IsInt64Ones(va) { + return fastnum.AppendFloat64Ones(dst, len(va)) + } + for _, v := range va { + f := float64(v) + dst = append(dst, f) + } + return dst + } + // increase conversion precision for negative exponents by dividing by e10 - isMul := true if e < 0 { - e = -e - isMul = false + e10 := math.Pow10(int(-e)) + for _, v := range va { + f := float64(v) / e10 + dst = append(dst, f) + } + return dst } e10 := math.Pow10(int(e)) for _, v := range va { - // Manually inline ToFloat here for better performance - f := float64(v) - if isMul { - f *= e10 - } else { - f /= e10 - } - if v == vInfPos { - f = infPos - } else if v == vInfNeg { - f = infNeg - } + f := float64(v) * e10 dst = append(dst, f) } return dst @@ -263,26 +258,12 @@ func maxUpExponent(v int64) int16 { // ToFloat returns f=v*10^e. func ToFloat(v int64, e int16) float64 { - // increase conversion precision for negative exponents by dividing by e10 - isMul := true - if e < 0 { - e = -e - isMul = false - } - e10 := math.Pow10(int(e)) f := float64(v) - if isMul { - f *= e10 - } else { - f /= e10 + // increase conversion precision for negative exponents by dividing by e10 + if e < 0 { + return f / math.Pow10(int(-e)) } - if v == vInfPos { - return infPos - } - if v == vInfNeg { - return infNeg - } - return f + return f * math.Pow10(int(e)) } const ( @@ -293,11 +274,6 @@ const ( vMin = -1<<63 + 1 ) -var ( - infPos = math.Inf(1) - infNeg = math.Inf(-1) -) - // FromFloat converts f to v*10^e. // // It tries minimizing v. @@ -327,7 +303,7 @@ func FromFloat(f float64) (int64, int16) { } func fromFloatInf(f float64) (int64, int16) { - // Special case for Inf + // Limit infs by max and min values for int64 if math.IsInf(f, 1) { return vInfPos, 0 } diff --git a/lib/decimal/decimal_test.go b/lib/decimal/decimal_test.go index fd1c5b842..1791a2408 100644 --- a/lib/decimal/decimal_test.go +++ b/lib/decimal/decimal_test.go @@ -55,7 +55,16 @@ func TestAppendDecimalToFloat(t *testing.T) { testAppendDecimalToFloat(t, []int64{0}, -10, []float64{0}) testAppendDecimalToFloat(t, []int64{-1, -10, 0, 100}, 2, []float64{-1e2, -1e3, 0, 1e4}) testAppendDecimalToFloat(t, []int64{-1, -10, 0, 100}, -2, []float64{-1e-2, -1e-1, 0, 1}) - testAppendDecimalToFloat(t, []int64{874957, 1130435}, -9, []float64{0.000874957, 0.001130435}) + testAppendDecimalToFloat(t, []int64{874957, 1130435}, -5, []float64{8.74957, 1.130435e1}) + testAppendDecimalToFloat(t, []int64{874957, 1130435}, -6, []float64{8.74957e-1, 1.130435}) + testAppendDecimalToFloat(t, []int64{874957, 1130435}, -7, []float64{8.74957e-2, 1.130435e-1}) + testAppendDecimalToFloat(t, []int64{874957, 1130435}, -8, []float64{8.74957e-3, 1.130435e-2}) + testAppendDecimalToFloat(t, []int64{874957, 1130435}, -9, []float64{8.74957e-4, 1.130435e-3}) + testAppendDecimalToFloat(t, []int64{874957, 1130435}, -10, []float64{8.74957e-5, 1.130435e-4}) + testAppendDecimalToFloat(t, []int64{874957, 1130435}, -11, []float64{8.74957e-6, 1.130435e-5}) + testAppendDecimalToFloat(t, []int64{874957, 1130435}, -12, []float64{8.74957e-7, 1.130435e-6}) + testAppendDecimalToFloat(t, []int64{874957, 1130435}, -13, []float64{8.74957e-8, 1.130435e-7}) + testAppendDecimalToFloat(t, []int64{vInfPos, vInfNeg, 1, 2}, 0, []float64{9.223372036854776e+18, -9.223372036854776e+18, 1, 2}) } func testAppendDecimalToFloat(t *testing.T, va []int64, e int16, fExpected []float64) { @@ -97,7 +106,7 @@ func TestCalibrateScale(t *testing.T) { testCalibrateScale(t, []int64{vInfPos, 1200}, []int64{500, 100}, 0, 2, []int64{vInfPos, 1200}, []int64{500e2, 100e2}, 0) testCalibrateScale(t, []int64{vInfPos, 1200}, []int64{500, 100}, 0, -2, []int64{vInfPos, 1200}, []int64{5, 1}, 0) testCalibrateScale(t, []int64{vInfPos, 1200}, []int64{3500, 100}, 0, -3, []int64{vInfPos, 1200}, []int64{3, 0}, 0) - testCalibrateScale(t, []int64{vInfPos, 1200}, []int64{35, 1}, 0, 40, []int64{vInfPos, 0}, []int64{35e17, 1e17}, 23) + testCalibrateScale(t, []int64{vInfPos, 1200}, []int64{35, 1}, 0, 40, []int64{0, 0}, []int64{35e17, 1e17}, 23) testCalibrateScale(t, []int64{vInfPos, 1200}, []int64{35, 1}, 40, 0, []int64{vInfPos, 1200}, []int64{0, 0}, 40) testCalibrateScale(t, []int64{vInfNeg, 1200}, []int64{35, 1}, 35, -5, []int64{vInfNeg, 1200}, []int64{0, 0}, 35) testCalibrateScale(t, []int64{vMax, vMin, 123}, []int64{100}, 0, 3, []int64{vMax, vMin, 123}, []int64{100e3}, 0) @@ -163,6 +172,8 @@ func TestMaxUpExponent(t *testing.T) { } } + f(vInfPos, 0) + f(vInfNeg, 0) f(0, 1024) f(-1<<63, 0) f((-1<<63)+1, 0) @@ -210,6 +221,9 @@ func TestAppendFloatToDecimal(t *testing.T) { // no-op testAppendFloatToDecimal(t, []float64{}, nil, 0) testAppendFloatToDecimal(t, []float64{0}, []int64{0}, 0) + testAppendFloatToDecimal(t, []float64{infPos, infNeg, 123}, []int64{vInfPos, vInfNeg, 123}, 0) + testAppendFloatToDecimal(t, []float64{infPos, infNeg, 123, 1e-4, 1e32}, []int64{92233, -92233, 0, 0, 1000000000000000000}, 14) + testAppendFloatToDecimal(t, []float64{float64(vInfPos), float64(vInfNeg), 123}, []int64{9223372036854775000, -9223372036854775000, 123}, 0) testAppendFloatToDecimal(t, []float64{0, -0, 1, -1, 12345678, -123456789}, []int64{0, 0, 1, -1, 12345678, -123456789}, 0) // upExp @@ -330,10 +344,12 @@ func TestFloatToDecimalRoundtrip(t *testing.T) { f(0.000874957) f(0.001130435) - f(math.Inf(1)) - f(math.Inf(-1)) - f(1<<63 - 1) - f(-1 << 63) + f(2933434554455e245) + f(3439234258934e-245) + f(float64(vInfPos)) + f(float64(vInfNeg)) + f(infPos) + f(infNeg) for i := 0; i < 1e4; i++ { v := rand.NormFloat64() @@ -357,9 +373,26 @@ func roundFloat(f float64, exp int) float64 { } func equalFloat(f1, f2 float64) bool { + f1 = adjustInf(f1) + f2 = adjustInf(f2) if math.IsInf(f1, 0) { return math.IsInf(f1, 1) == math.IsInf(f2, 1) || math.IsInf(f1, -1) == math.IsInf(f2, -1) } eps := math.Abs(f1 - f2) return eps == 0 || eps*conversionPrecision < math.Abs(f1)+math.Abs(f2) } + +func adjustInf(f float64) float64 { + if f == float64(vInfPos) { + return infPos + } + if f == float64(vInfNeg) { + return infNeg + } + return f +} + +var ( + infPos = math.Inf(1) + infNeg = math.Inf(-1) +) diff --git a/lib/decimal/decimal_timing_test.go b/lib/decimal/decimal_timing_test.go index 16e844a19..ceba29d2f 100644 --- a/lib/decimal/decimal_timing_test.go +++ b/lib/decimal/decimal_timing_test.go @@ -9,26 +9,26 @@ import ( func BenchmarkAppendDecimalToFloat(b *testing.B) { b.Run("RealFloat", func(b *testing.B) { - benchmarkAppendDecimalToFloat(b, testVA) + benchmarkAppendDecimalToFloat(b, testVA, vaScale) }) b.Run("Integers", func(b *testing.B) { - benchmarkAppendDecimalToFloat(b, testIntegers) + benchmarkAppendDecimalToFloat(b, testIntegers, integersScale) }) b.Run("Zeros", func(b *testing.B) { - benchmarkAppendDecimalToFloat(b, testZeros) + benchmarkAppendDecimalToFloat(b, testZeros, 0) }) b.Run("Ones", func(b *testing.B) { - benchmarkAppendDecimalToFloat(b, testOnes) + benchmarkAppendDecimalToFloat(b, testOnes, 0) }) } -func benchmarkAppendDecimalToFloat(b *testing.B, a []int64) { +func benchmarkAppendDecimalToFloat(b *testing.B, a []int64, scale int16) { b.ReportAllocs() b.SetBytes(int64(len(a))) b.RunParallel(func(pb *testing.PB) { var fa []float64 for pb.Next() { - fa = AppendDecimalToFloat(fa[:0], a, 0) + fa = AppendDecimalToFloat(fa[:0], a, scale) atomic.AddUint64(&Sink, uint64(len(fa))) } }) @@ -99,15 +99,8 @@ var testFAInteger = func() []float64 { return fa }() -var testVA = func() []int64 { - va, _ := AppendFloatToDecimal(nil, testFAReal) - return va -}() - -var testIntegers = func() []int64 { - va, _ := AppendFloatToDecimal(nil, testFAInteger) - return va -}() +var testVA, vaScale = AppendFloatToDecimal(nil, testFAReal) +var testIntegers, integersScale = AppendFloatToDecimal(nil, testFAInteger) func BenchmarkFromFloat(b *testing.B) { for _, f := range []float64{0, 1234, 12334345, 12343.4344, 123.45678901e12, 12.3454435e30} {