mirror of
https://github.com/golang/go
synced 2024-11-18 13:04:46 -07: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:
parent
c396c047c6
commit
16b3675bc8
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user