lib/decimal: reduce rounding error when converting from decimal to float with negative exponent

While at it, slightly increase the conversion performance by moving fast path to the top of the loop.
This commit is contained in:
Aliaksandr Valialkin 2019-11-19 23:32:13 +02:00
parent c03b87dac0
commit 75eeea21ee
3 changed files with 42 additions and 6 deletions

View file

@ -93,16 +93,25 @@ func AppendDecimalToFloat(dst []float64, va []int64, e int16) []float64 {
// Extend dst capacity in order to eliminate memory allocations below.
dst = ExtendFloat64sCapacity(dst, len(va))
// 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 {
// Manually inline ToFloat here for better performance
var f float64
f := float64(v)
if isMul {
f *= e10
} else {
f /= e10
}
if v == vInfPos {
f = infPos
} else if v == vInfNeg {
f = infNeg
} else {
f = float64(v) * e10
}
dst = append(dst, f)
}
@ -254,13 +263,26 @@ 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
}
if v == vInfPos {
return infPos
}
if v == vInfNeg {
return infNeg
}
return float64(v) * math.Pow10(int(e))
return f
}
const (

View file

@ -43,6 +43,9 @@ func TestPositiveFloatToDecimal(t *testing.T) {
f(1234567890123456789e-14, 1234567890123, -8)
f(1234567890123456789e-17, 12345678901234, -12)
f(1234567890123456789e-20, 1234567890123, -14)
f(0.000874957, 874957, -9)
f(0.001130435, 1130435, -9)
}
func TestAppendDecimalToFloat(t *testing.T) {
@ -52,6 +55,7 @@ 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})
}
func testAppendDecimalToFloat(t *testing.T, va []int64, e int16, fExpected []float64) {
@ -323,6 +327,8 @@ func TestFloatToDecimalRoundtrip(t *testing.T) {
f(12.34567890125)
f(-1234567.8901256789)
f(15e18)
f(0.000874957)
f(0.001130435)
f(math.Inf(1))
f(math.Inf(-1))

View file

@ -8,9 +8,12 @@ import (
)
func BenchmarkAppendDecimalToFloat(b *testing.B) {
b.Run("VarNums", func(b *testing.B) {
b.Run("RealFloat", func(b *testing.B) {
benchmarkAppendDecimalToFloat(b, testVA)
})
b.Run("Integers", func(b *testing.B) {
benchmarkAppendDecimalToFloat(b, testIntegers)
})
b.Run("Zeros", func(b *testing.B) {
benchmarkAppendDecimalToFloat(b, testZeros)
})
@ -83,7 +86,7 @@ func benchmarkAppendFloatToDecimal(b *testing.B, fa []float64) {
var testFAReal = func() []float64 {
fa := make([]float64, 8*1024)
for i := 0; i < len(fa); i++ {
fa[i] = rand.NormFloat64() * 1e6
fa[i] = rand.NormFloat64() * 1e-6
}
return fa
}()
@ -101,6 +104,11 @@ var testVA = func() []int64 {
return va
}()
var testIntegers = func() []int64 {
va, _ := AppendFloatToDecimal(nil, testFAInteger)
return va
}()
func BenchmarkFromFloat(b *testing.B) {
for _, f := range []float64{0, 1234, 12334345, 12343.4344, 123.45678901e12, 12.3454435e30} {
b.Run(fmt.Sprintf("%g", f), func(b *testing.B) {