1
0
mirror of https://github.com/golang/go synced 2024-09-30 16:28:32 -06:00

math/big: optimize Float.Parse by reducing powers of 10 to powers of 2 and 5

Instead of computing the final adjustment factor as a power of 10,
it's more efficient to split 10**e into 2**e * 5**e . Powers of 2
are trivially added to the Float exponent, and powers of 5 are
smaller and thus faster to compute.

Also, use a table of uint64 values rather than float64 values for
initial power value. uint64 values appear to be faster to convert
to Floats (useful for small exponents).

Added two small benchmarks to confirm that there's no regresssion.

benchmark                         old ns/op     new ns/op     delta
BenchmarkParseFloatSmallExp-8     17543         16220         -7.54%
BenchmarkParseFloatLargeExp-8     60865         59996         -1.43%

Change-Id: I3efd7556b023316f86f334137a67fe0c6d52f8ef
Reviewed-on: https://go-review.googlesource.com/14782
Reviewed-by: Alan Donovan <adonovan@google.com>
This commit is contained in:
Robert Griesemer 2015-09-19 18:24:16 -07:00
parent c396c047c6
commit 16b3675bc8
2 changed files with 137 additions and 37 deletions

View File

@ -72,37 +72,46 @@ func (z *Float) scan(r io.ByteScanner, base int) (f *Float, b int, err error) {
// ebase**exp. Finally, mantissa normalization (shift left) requires
// a correcting multiplication by 2**(-shiftcount). Multiplications
// are commutative, so we can apply them in any order as long as there
// is no loss of precision. We only have powers of 2 and 10; keep
// track via separate exponents exp2 and exp10.
// is no loss of precision. We only have powers of 2 and 10, and
// we split powers of 10 into the product of the same powers of
// 2 and 5. This reduces the size of the multiplication factor
// needed for base-10 exponents.
// normalize mantissa and get initial binary exponent
var exp2 = int64(len(z.mant))*_W - fnorm(z.mant)
// normalize mantissa and determine initial exponent contributions
exp2 := int64(len(z.mant))*_W - fnorm(z.mant)
exp5 := int64(0)
// determine binary or decimal exponent contribution of decimal point
var exp10 int64
if fcount < 0 {
// The mantissa has a "decimal" point ddd.dddd; and
// -fcount is the number of digits to the right of '.'.
// Adjust relevant exponent accodingly.
d := int64(fcount)
switch b {
case 16:
fcount *= 4 // hexadecimal digits are 4 bits each
fallthrough
case 10:
exp5 = d
fallthrough // 10**e == 5**e * 2**e
case 2:
exp2 += int64(fcount)
default: // b == 10
exp10 = int64(fcount)
exp2 += d
case 16:
exp2 += d * 4 // hexadecimal digits are 4 bits each
default:
panic("unexpected mantissa base")
}
// we don't need fcount anymore
// fcount consumed - not needed anymore
}
// take actual exponent into account
if ebase == 2 {
switch ebase {
case 10:
exp5 += exp
fallthrough
case 2:
exp2 += exp
} else { // ebase == 10
exp10 += exp
default:
panic("unexpected exponent base")
}
// we don't need exp anymore
// exp consumed - not needed anymore
// apply 2**exp2
if MinExp <= exp2 && exp2 <= MaxExp {
@ -115,49 +124,76 @@ func (z *Float) scan(r io.ByteScanner, base int) (f *Float, b int, err error) {
return
}
if exp10 == 0 {
// no decimal exponent to consider
if exp5 == 0 {
// no decimal exponent contribution
z.round(0)
return
}
// exp10 != 0
// exp5 != 0
// apply 10**exp10
// apply 5**exp5
p := new(Float).SetPrec(z.Prec() + 64) // use more bits for p -- TODO(gri) what is the right number?
if exp10 < 0 {
z.Quo(z, p.pow10(-exp10))
if exp5 < 0 {
z.Quo(z, p.pow5(uint64(-exp5)))
} else {
z.Mul(z, p.pow10(exp10))
z.Mul(z, p.pow5(uint64(exp5)))
}
return
}
// These powers of 10 can be represented exactly as a float64.
var pow10tab = [...]float64{
1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9,
1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19,
// These powers of 5 fit into a uint64.
//
// for p, q := uint64(0), uint64(1); p < q; p, q = q, q*5 {
// fmt.Println(q)
// }
//
var pow5tab = [...]uint64{
1,
5,
25,
125,
625,
3125,
15625,
78125,
390625,
1953125,
9765625,
48828125,
244140625,
1220703125,
6103515625,
30517578125,
152587890625,
762939453125,
3814697265625,
19073486328125,
95367431640625,
476837158203125,
2384185791015625,
11920928955078125,
59604644775390625,
298023223876953125,
1490116119384765625,
7450580596923828125,
}
// pow10 sets z to 10**n and returns z.
// pow5 sets z to 5**n and returns z.
// n must not be negative.
func (z *Float) pow10(n int64) *Float {
if n < 0 {
panic("pow10 called with negative argument")
}
const m = int64(len(pow10tab) - 1)
func (z *Float) pow5(n uint64) *Float {
const m = uint64(len(pow5tab) - 1)
if n <= m {
return z.SetFloat64(pow10tab[n])
return z.SetUint64(pow5tab[n])
}
// n > m
z.SetFloat64(pow10tab[m])
z.SetUint64(pow5tab[m])
n -= m
// use more bits for f than for z
// TODO(gri) what is the right number?
f := new(Float).SetPrec(z.Prec() + 64).SetInt64(10)
f := new(Float).SetPrec(z.Prec() + 64).SetUint64(5)
for n > 0 {
if n&1 != 0 {

View File

@ -571,3 +571,67 @@ func TestFloatFormat(t *testing.T) {
}
}
}
func BenchmarkParseFloatSmallExp(b *testing.B) {
for i := 0; i < b.N; i++ {
for _, s := range []string{
"1e0",
"1e-1",
"1e-2",
"1e-3",
"1e-4",
"1e-5",
"1e-10",
"1e-20",
"1e-50",
"1e1",
"1e2",
"1e3",
"1e4",
"1e5",
"1e10",
"1e20",
"1e50",
} {
var x Float
_, _, err := x.Parse(s, 0)
if err != nil {
b.Fatalf("%s: %v", s, err)
}
}
}
}
func BenchmarkParseFloatLargeExp(b *testing.B) {
for i := 0; i < b.N; i++ {
for _, s := range []string{
"1e0",
"1e-10",
"1e-20",
"1e-30",
"1e-40",
"1e-50",
"1e-100",
"1e-500",
"1e-1000",
"1e-5000",
"1e-10000",
"1e10",
"1e20",
"1e30",
"1e40",
"1e50",
"1e100",
"1e500",
"1e1000",
"1e5000",
"1e10000",
} {
var x Float
_, _, err := x.Parse(s, 0)
if err != nil {
b.Fatalf("%s: %v", s, err)
}
}
}
}