mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-21 14:44:00 +00:00
lib/decimal: increase float->decimal conversion precision for big numbers
Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/213
This commit is contained in:
parent
b0295dbf2e
commit
a42b5db39f
2 changed files with 61 additions and 11 deletions
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue