lib/decimal: speed up FromFloat for common case with integers

This commit is contained in:
Aliaksandr Valialkin 2019-10-31 13:24:47 +02:00
parent cfa5e279c2
commit e76e21e4c7
2 changed files with 54 additions and 44 deletions

View File

@ -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
}

View File

@ -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)