mirror of
https://github.com/golang/go
synced 2024-11-20 00:34:43 -07:00
faa9d1eca9
The existing code used ints for the (slow) decimal conversion and assumed that they were 32bit wide. This change uses uints and the appropriate width (32 or 64bit) depending on platform. The performance difference is in the noise for the usual (optimized) case which does not use the slow path conversion: benchmark old ns/op new ns/op delta BenchmarkFormatFloatDecimal 298 299 +0.34% BenchmarkFormatFloat 388 392 +1.03% BenchmarkFormatFloatExp 365 364 -0.27% BenchmarkFormatFloatNegExp 364 362 -0.55% BenchmarkFormatFloatBig 482 476 -1.24% BenchmarkAppendFloatDecimal 100 102 +2.00% BenchmarkAppendFloat 199 201 +1.01% BenchmarkAppendFloatExp 174 175 +0.57% BenchmarkAppendFloatNegExp 169 174 +2.96% BenchmarkAppendFloatBig 286 286 +0.00% BenchmarkAppendFloat32Integer 99.9 102 +2.10% BenchmarkAppendFloat32ExactFraction 161 164 +1.86% BenchmarkAppendFloat32Point 199 201 +1.01% BenchmarkAppendFloat32Exp 167 168 +0.60% BenchmarkAppendFloat32NegExp 163 169 +3.68% BenchmarkAppendFloat64Fixed1 137 134 -2.19% BenchmarkAppendFloat64Fixed2 144 146 +1.39% BenchmarkAppendFloat64Fixed3 138 140 +1.45% BenchmarkAppendFloat64Fixed4 144 145 +0.69% The performance difference is significant if the fast path conversion is explicitly turned off (ftoa.go:101): benchmark old ns/op new ns/op delta BenchmarkFormatFloatDecimal 459 427 -6.97% BenchmarkFormatFloat 1560 1180 -24.36% BenchmarkFormatFloatExp 5501 3128 -43.14% BenchmarkFormatFloatNegExp 24085 14360 -40.38% BenchmarkFormatFloatBig 1409 1081 -23.28% BenchmarkAppendFloatDecimal 248 226 -8.87% BenchmarkAppendFloat 1315 982 -25.32% BenchmarkAppendFloatExp 5274 2869 -45.60% BenchmarkAppendFloatNegExp 23905 14054 -41.21% BenchmarkAppendFloatBig 1194 860 -27.97% BenchmarkAppendFloat32Integer 167 175 +4.79% BenchmarkAppendFloat32ExactFraction 182 184 +1.10% BenchmarkAppendFloat32Point 556 564 +1.44% BenchmarkAppendFloat32Exp 1134 918 -19.05% BenchmarkAppendFloat32NegExp 2679 1801 -32.77% BenchmarkAppendFloat64Fixed1 274 238 -13.14% BenchmarkAppendFloat64Fixed2 494 368 -25.51% BenchmarkAppendFloat64Fixed3 1833 1008 -45.01% BenchmarkAppendFloat64Fixed4 6133 3596 -41.37% Change-Id: I829b8abcca882b1c10d8ae421d3249597c31f3c9 Reviewed-on: https://go-review.googlesource.com/3811 Reviewed-by: Russ Cox <rsc@golang.org>
414 lines
11 KiB
Go
414 lines
11 KiB
Go
// Copyright 2009 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// Multiprecision decimal numbers.
|
|
// For floating-point formatting only; not general purpose.
|
|
// Only operations are assign and (binary) left/right shift.
|
|
// Can do binary floating point in multiprecision decimal precisely
|
|
// because 2 divides 10; cannot do decimal floating point
|
|
// in multiprecision binary precisely.
|
|
|
|
package strconv
|
|
|
|
type decimal struct {
|
|
d [800]byte // digits, big-endian representation
|
|
nd int // number of digits used
|
|
dp int // decimal point
|
|
neg bool
|
|
trunc bool // discarded nonzero digits beyond d[:nd]
|
|
}
|
|
|
|
func (a *decimal) String() string {
|
|
n := 10 + a.nd
|
|
if a.dp > 0 {
|
|
n += a.dp
|
|
}
|
|
if a.dp < 0 {
|
|
n += -a.dp
|
|
}
|
|
|
|
buf := make([]byte, n)
|
|
w := 0
|
|
switch {
|
|
case a.nd == 0:
|
|
return "0"
|
|
|
|
case a.dp <= 0:
|
|
// zeros fill space between decimal point and digits
|
|
buf[w] = '0'
|
|
w++
|
|
buf[w] = '.'
|
|
w++
|
|
w += digitZero(buf[w : w+-a.dp])
|
|
w += copy(buf[w:], a.d[0:a.nd])
|
|
|
|
case a.dp < a.nd:
|
|
// decimal point in middle of digits
|
|
w += copy(buf[w:], a.d[0:a.dp])
|
|
buf[w] = '.'
|
|
w++
|
|
w += copy(buf[w:], a.d[a.dp:a.nd])
|
|
|
|
default:
|
|
// zeros fill space between digits and decimal point
|
|
w += copy(buf[w:], a.d[0:a.nd])
|
|
w += digitZero(buf[w : w+a.dp-a.nd])
|
|
}
|
|
return string(buf[0:w])
|
|
}
|
|
|
|
func digitZero(dst []byte) int {
|
|
for i := range dst {
|
|
dst[i] = '0'
|
|
}
|
|
return len(dst)
|
|
}
|
|
|
|
// trim trailing zeros from number.
|
|
// (They are meaningless; the decimal point is tracked
|
|
// independent of the number of digits.)
|
|
func trim(a *decimal) {
|
|
for a.nd > 0 && a.d[a.nd-1] == '0' {
|
|
a.nd--
|
|
}
|
|
if a.nd == 0 {
|
|
a.dp = 0
|
|
}
|
|
}
|
|
|
|
// Assign v to a.
|
|
func (a *decimal) Assign(v uint64) {
|
|
var buf [24]byte
|
|
|
|
// Write reversed decimal in buf.
|
|
n := 0
|
|
for v > 0 {
|
|
v1 := v / 10
|
|
v -= 10 * v1
|
|
buf[n] = byte(v + '0')
|
|
n++
|
|
v = v1
|
|
}
|
|
|
|
// Reverse again to produce forward decimal in a.d.
|
|
a.nd = 0
|
|
for n--; n >= 0; n-- {
|
|
a.d[a.nd] = buf[n]
|
|
a.nd++
|
|
}
|
|
a.dp = a.nd
|
|
trim(a)
|
|
}
|
|
|
|
// Maximum shift that we can do in one pass without overflow.
|
|
// A uint has 32 or 64 bits, and we have to be able to accommodate 9<<k.
|
|
const uintSize = 32 << (^uint(0) >> 63)
|
|
const maxShift = uintSize - 4
|
|
|
|
// Binary shift right (/ 2) by k bits. k <= maxShift to avoid overflow.
|
|
func rightShift(a *decimal, k uint) {
|
|
r := 0 // read pointer
|
|
w := 0 // write pointer
|
|
|
|
// Pick up enough leading digits to cover first shift.
|
|
var n uint
|
|
for ; n>>k == 0; r++ {
|
|
if r >= a.nd {
|
|
if n == 0 {
|
|
// a == 0; shouldn't get here, but handle anyway.
|
|
a.nd = 0
|
|
return
|
|
}
|
|
for n>>k == 0 {
|
|
n = n * 10
|
|
r++
|
|
}
|
|
break
|
|
}
|
|
c := uint(a.d[r])
|
|
n = n*10 + c - '0'
|
|
}
|
|
a.dp -= r - 1
|
|
|
|
// Pick up a digit, put down a digit.
|
|
for ; r < a.nd; r++ {
|
|
c := uint(a.d[r])
|
|
dig := n >> k
|
|
n -= dig << k
|
|
a.d[w] = byte(dig + '0')
|
|
w++
|
|
n = n*10 + c - '0'
|
|
}
|
|
|
|
// Put down extra digits.
|
|
for n > 0 {
|
|
dig := n >> k
|
|
n -= dig << k
|
|
if w < len(a.d) {
|
|
a.d[w] = byte(dig + '0')
|
|
w++
|
|
} else if dig > 0 {
|
|
a.trunc = true
|
|
}
|
|
n = n * 10
|
|
}
|
|
|
|
a.nd = w
|
|
trim(a)
|
|
}
|
|
|
|
// Cheat sheet for left shift: table indexed by shift count giving
|
|
// number of new digits that will be introduced by that shift.
|
|
//
|
|
// For example, leftcheats[4] = {2, "625"}. That means that
|
|
// if we are shifting by 4 (multiplying by 16), it will add 2 digits
|
|
// when the string prefix is "625" through "999", and one fewer digit
|
|
// if the string prefix is "000" through "624".
|
|
//
|
|
// Credit for this trick goes to Ken.
|
|
|
|
type leftCheat struct {
|
|
delta int // number of new digits
|
|
cutoff string // minus one digit if original < a.
|
|
}
|
|
|
|
var leftcheats = []leftCheat{
|
|
// Leading digits of 1/2^i = 5^i.
|
|
// 5^23 is not an exact 64-bit floating point number,
|
|
// so have to use bc for the math.
|
|
// Go up to 60 to be large enough for 32bit and 64bit platforms.
|
|
/*
|
|
seq 60 | sed 's/^/5^/' | bc |
|
|
awk 'BEGIN{ print "\t{ 0, \"\" }," }
|
|
{
|
|
log2 = log(2)/log(10)
|
|
printf("\t{ %d, \"%s\" },\t// * %d\n",
|
|
int(log2*NR+1), $0, 2**NR)
|
|
}'
|
|
*/
|
|
{0, ""},
|
|
{1, "5"}, // * 2
|
|
{1, "25"}, // * 4
|
|
{1, "125"}, // * 8
|
|
{2, "625"}, // * 16
|
|
{2, "3125"}, // * 32
|
|
{2, "15625"}, // * 64
|
|
{3, "78125"}, // * 128
|
|
{3, "390625"}, // * 256
|
|
{3, "1953125"}, // * 512
|
|
{4, "9765625"}, // * 1024
|
|
{4, "48828125"}, // * 2048
|
|
{4, "244140625"}, // * 4096
|
|
{4, "1220703125"}, // * 8192
|
|
{5, "6103515625"}, // * 16384
|
|
{5, "30517578125"}, // * 32768
|
|
{5, "152587890625"}, // * 65536
|
|
{6, "762939453125"}, // * 131072
|
|
{6, "3814697265625"}, // * 262144
|
|
{6, "19073486328125"}, // * 524288
|
|
{7, "95367431640625"}, // * 1048576
|
|
{7, "476837158203125"}, // * 2097152
|
|
{7, "2384185791015625"}, // * 4194304
|
|
{7, "11920928955078125"}, // * 8388608
|
|
{8, "59604644775390625"}, // * 16777216
|
|
{8, "298023223876953125"}, // * 33554432
|
|
{8, "1490116119384765625"}, // * 67108864
|
|
{9, "7450580596923828125"}, // * 134217728
|
|
{9, "37252902984619140625"}, // * 268435456
|
|
{9, "186264514923095703125"}, // * 536870912
|
|
{10, "931322574615478515625"}, // * 1073741824
|
|
{10, "4656612873077392578125"}, // * 2147483648
|
|
{10, "23283064365386962890625"}, // * 4294967296
|
|
{10, "116415321826934814453125"}, // * 8589934592
|
|
{11, "582076609134674072265625"}, // * 17179869184
|
|
{11, "2910383045673370361328125"}, // * 34359738368
|
|
{11, "14551915228366851806640625"}, // * 68719476736
|
|
{12, "72759576141834259033203125"}, // * 137438953472
|
|
{12, "363797880709171295166015625"}, // * 274877906944
|
|
{12, "1818989403545856475830078125"}, // * 549755813888
|
|
{13, "9094947017729282379150390625"}, // * 1099511627776
|
|
{13, "45474735088646411895751953125"}, // * 2199023255552
|
|
{13, "227373675443232059478759765625"}, // * 4398046511104
|
|
{13, "1136868377216160297393798828125"}, // * 8796093022208
|
|
{14, "5684341886080801486968994140625"}, // * 17592186044416
|
|
{14, "28421709430404007434844970703125"}, // * 35184372088832
|
|
{14, "142108547152020037174224853515625"}, // * 70368744177664
|
|
{15, "710542735760100185871124267578125"}, // * 140737488355328
|
|
{15, "3552713678800500929355621337890625"}, // * 281474976710656
|
|
{15, "17763568394002504646778106689453125"}, // * 562949953421312
|
|
{16, "88817841970012523233890533447265625"}, // * 1125899906842624
|
|
{16, "444089209850062616169452667236328125"}, // * 2251799813685248
|
|
{16, "2220446049250313080847263336181640625"}, // * 4503599627370496
|
|
{16, "11102230246251565404236316680908203125"}, // * 9007199254740992
|
|
{17, "55511151231257827021181583404541015625"}, // * 18014398509481984
|
|
{17, "277555756156289135105907917022705078125"}, // * 36028797018963968
|
|
{17, "1387778780781445675529539585113525390625"}, // * 72057594037927936
|
|
{18, "6938893903907228377647697925567626953125"}, // * 144115188075855872
|
|
{18, "34694469519536141888238489627838134765625"}, // * 288230376151711744
|
|
{18, "173472347597680709441192448139190673828125"}, // * 576460752303423488
|
|
{19, "867361737988403547205962240695953369140625"}, // * 1152921504606846976
|
|
}
|
|
|
|
// Is the leading prefix of b lexicographically less than s?
|
|
func prefixIsLessThan(b []byte, s string) bool {
|
|
for i := 0; i < len(s); i++ {
|
|
if i >= len(b) {
|
|
return true
|
|
}
|
|
if b[i] != s[i] {
|
|
return b[i] < s[i]
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Binary shift left (* 2) by k bits. k <= maxShift to avoid overflow.
|
|
func leftShift(a *decimal, k uint) {
|
|
delta := leftcheats[k].delta
|
|
if prefixIsLessThan(a.d[0:a.nd], leftcheats[k].cutoff) {
|
|
delta--
|
|
}
|
|
|
|
r := a.nd // read index
|
|
w := a.nd + delta // write index
|
|
|
|
// Pick up a digit, put down a digit.
|
|
var n uint
|
|
for r--; r >= 0; r-- {
|
|
n += (uint(a.d[r]) - '0') << k
|
|
quo := n / 10
|
|
rem := n - 10*quo
|
|
w--
|
|
if w < len(a.d) {
|
|
a.d[w] = byte(rem + '0')
|
|
} else if rem != 0 {
|
|
a.trunc = true
|
|
}
|
|
n = quo
|
|
}
|
|
|
|
// Put down extra digits.
|
|
for n > 0 {
|
|
quo := n / 10
|
|
rem := n - 10*quo
|
|
w--
|
|
if w < len(a.d) {
|
|
a.d[w] = byte(rem + '0')
|
|
} else if rem != 0 {
|
|
a.trunc = true
|
|
}
|
|
n = quo
|
|
}
|
|
|
|
a.nd += delta
|
|
if a.nd >= len(a.d) {
|
|
a.nd = len(a.d)
|
|
}
|
|
a.dp += delta
|
|
trim(a)
|
|
}
|
|
|
|
// Binary shift left (k > 0) or right (k < 0).
|
|
func (a *decimal) Shift(k int) {
|
|
switch {
|
|
case a.nd == 0:
|
|
// nothing to do: a == 0
|
|
case k > 0:
|
|
for k > maxShift {
|
|
leftShift(a, maxShift)
|
|
k -= maxShift
|
|
}
|
|
leftShift(a, uint(k))
|
|
case k < 0:
|
|
for k < -maxShift {
|
|
rightShift(a, maxShift)
|
|
k += maxShift
|
|
}
|
|
rightShift(a, uint(-k))
|
|
}
|
|
}
|
|
|
|
// If we chop a at nd digits, should we round up?
|
|
func shouldRoundUp(a *decimal, nd int) bool {
|
|
if nd < 0 || nd >= a.nd {
|
|
return false
|
|
}
|
|
if a.d[nd] == '5' && nd+1 == a.nd { // exactly halfway - round to even
|
|
// if we truncated, a little higher than what's recorded - always round up
|
|
if a.trunc {
|
|
return true
|
|
}
|
|
return nd > 0 && (a.d[nd-1]-'0')%2 != 0
|
|
}
|
|
// not halfway - digit tells all
|
|
return a.d[nd] >= '5'
|
|
}
|
|
|
|
// Round a to nd digits (or fewer).
|
|
// If nd is zero, it means we're rounding
|
|
// just to the left of the digits, as in
|
|
// 0.09 -> 0.1.
|
|
func (a *decimal) Round(nd int) {
|
|
if nd < 0 || nd >= a.nd {
|
|
return
|
|
}
|
|
if shouldRoundUp(a, nd) {
|
|
a.RoundUp(nd)
|
|
} else {
|
|
a.RoundDown(nd)
|
|
}
|
|
}
|
|
|
|
// Round a down to nd digits (or fewer).
|
|
func (a *decimal) RoundDown(nd int) {
|
|
if nd < 0 || nd >= a.nd {
|
|
return
|
|
}
|
|
a.nd = nd
|
|
trim(a)
|
|
}
|
|
|
|
// Round a up to nd digits (or fewer).
|
|
func (a *decimal) RoundUp(nd int) {
|
|
if nd < 0 || nd >= a.nd {
|
|
return
|
|
}
|
|
|
|
// round up
|
|
for i := nd - 1; i >= 0; i-- {
|
|
c := a.d[i]
|
|
if c < '9' { // can stop after this digit
|
|
a.d[i]++
|
|
a.nd = i + 1
|
|
return
|
|
}
|
|
}
|
|
|
|
// Number is all 9s.
|
|
// Change to single 1 with adjusted decimal point.
|
|
a.d[0] = '1'
|
|
a.nd = 1
|
|
a.dp++
|
|
}
|
|
|
|
// Extract integer part, rounded appropriately.
|
|
// No guarantees about overflow.
|
|
func (a *decimal) RoundedInteger() uint64 {
|
|
if a.dp > 20 {
|
|
return 0xFFFFFFFFFFFFFFFF
|
|
}
|
|
var i int
|
|
n := uint64(0)
|
|
for i = 0; i < a.dp && i < a.nd; i++ {
|
|
n = n*10 + uint64(a.d[i]-'0')
|
|
}
|
|
for ; i < a.dp; i++ {
|
|
n *= 10
|
|
}
|
|
if shouldRoundUp(a, a.dp) {
|
|
n++
|
|
}
|
|
return n
|
|
}
|