lib/decimal: increase float->decimal conversion precision for big numbers

Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/213
This commit is contained in:
Aliaksandr Valialkin 2019-10-28 13:23:27 +02:00
parent b0295dbf2e
commit a42b5db39f
2 changed files with 61 additions and 11 deletions

View file

@ -299,12 +299,18 @@ func FromFloat(f float64) (v int64, e int16) {
func positiveFloatToDecimal(f float64) (int64, int16) {
var scale int16
v := int64(f)
if f == float64(v) {
u := uint64(f)
if float64(u) == f {
// Fast path for integers.
u := uint64(v)
if u%10 != 0 {
return v, 0
for u >= 1<<54 {
// 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.
u /= 10
@ -317,9 +323,15 @@ func positiveFloatToDecimal(f float64) (int64, int16) {
}
// Slow path for floating point numbers.
prec := conversionPrecision
if f > 1e6 || f < 1e-6 {
// Normalize f, so it is in the small range suitable
// for the next loop.
if f > 1e6 {
// Increase conversion precision for big numbers.
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/213
prec = 1e15
}
_, exp := math.Frexp(f)
scale = int16(float64(exp) * math.Ln2 / math.Ln10)
f *= math.Pow10(-int(scale))
@ -327,20 +339,20 @@ func positiveFloatToDecimal(f float64) (int64, int16) {
// Multiply f by 100 until the fractional part becomes
// too small comparing to integer part.
for f < conversionPrecision {
for f < prec {
x, frac := math.Modf(f)
if frac*conversionPrecision < x {
if frac*prec < x {
f = x
break
}
if (1-frac)*conversionPrecision < x {
if (1-frac)*prec < x {
f = x + 1
break
}
f *= 100
scale -= 2
}
u := uint64(f)
u = uint64(f)
if u%10 != 0 {
return int64(u), scale
}

View file

@ -7,6 +7,44 @@ import (
"testing"
)
func TestPositiveFloatToDecimal(t *testing.T) {
f := func(f float64, decimalExpected int64, exponentExpected int16) {
t.Helper()
decimal, exponent := positiveFloatToDecimal(f)
if decimal != decimalExpected {
t.Fatalf("unexpected decimal for positiveFloatToDecimal(%f); got %d; want %d", f, decimal, decimalExpected)
}
if exponent != exponentExpected {
t.Fatalf("unexpected exponent for positiveFloatToDecimal(%f); got %d; want %d", f, exponent, exponentExpected)
}
}
f(0, 0, 0)
f(1, 1, 0)
f(30, 3, 1)
f(12345678900000000, 123456789, 8)
f(12345678901234567, 12345678901234568, 0)
f(1234567890123456789, 12345678901234567, 2)
f(12345678901234567890, 12345678901234567, 3)
f(18446744073670737131, 1844674407367073, 4)
f(123456789012345678901, 12345678901234568, 4)
f(1<<53, 1<<53, 0)
f(1<<54, 1801439850948198, 1)
f(1<<55, 3602879701896396, 1)
f(1<<62, 4611686018427387, 3)
f(1<<63, 9223372036854775, 3)
f(1<<64, 18446744073709548, 3)
f(1<<65, 368934881474191, 5)
f(1<<66, 737869762948382, 5)
f(1<<67, 1475739525896764, 5)
f(0.1, 1, -1)
f(123456789012345678e-5, 12345678901234568, -4)
f(1234567890123456789e-10, 12345678901234568, -8)
f(1234567890123456789e-14, 1234567890123, -8)
f(1234567890123456789e-17, 12345678901234, -12)
f(1234567890123456789e-20, 1234567890123, -14)
}
func TestAppendDecimalToFloat(t *testing.T) {
testAppendDecimalToFloat(t, []int64{}, 0, nil)
testAppendDecimalToFloat(t, []int64{0}, 0, []float64{0})
@ -248,8 +286,8 @@ func TestFloatToDecimal(t *testing.T) {
f(math.Inf(1), vInfPos, 0)
f(math.Inf(-1), vInfNeg, 0)
f(1<<63-1, 922337203685, 7)
f(-1<<63, -922337203685, 7)
f(1<<63-1, 9223372036854775, 3)
f(-1<<63, -9223372036854775, 3)
// Test precision loss due to conversionPrecision.
f(0.1234567890123456, 12345678901234, -14)