-
Notifications
You must be signed in to change notification settings - Fork 2.1k
/
format.go
308 lines (277 loc) · 7.32 KB
/
format.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
/*
Copyright 2022 The Vitess Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package decimal
func appendZeroes(buf []byte, n int) []byte {
const zeroes = "0000000000000000"
for n >= len(zeroes) {
buf = append(buf, zeroes...)
n -= len(zeroes)
}
return append(buf, zeroes[:n]...)
}
func trimTrailingZeroes(buf []byte) []byte {
var trim int
for trim = len(buf); trim > 0; trim-- {
if buf[trim-1] != '0' {
break
}
}
if buf[trim-1] == '.' {
trim--
}
return buf[:trim]
}
// formatSlow formats the decimal to its maximum accuracy without rounding.
// This is an unoptimized implementation left here for testing against the
// optimized implementations.
func (d *Decimal) formatSlow(trim bool) []byte {
var (
buf []byte
exp = int(d.exp)
integral, _ = d.value.MarshalText()
)
if exp >= 0 {
buf = make([]byte, 0, len(integral)+exp)
} else if len(integral) > -exp {
buf = make([]byte, 0, len(integral)+1)
} else {
buf = make([]byte, 0, len(integral)-exp+2)
}
if exp >= 0 {
buf = append(buf, integral...)
if d.value.Sign() != 0 {
buf = appendZeroes(buf, exp)
}
return buf
}
if integral[0] == '-' {
integral = integral[1:]
buf = append(buf, '-')
}
if len(integral) > -exp {
buf = append(buf, integral[:len(integral)+exp]...)
buf = append(buf, '.')
buf = append(buf, integral[len(integral)+exp:]...)
} else {
num0s := -exp - len(integral)
buf = append(buf, '0', '.')
buf = appendZeroes(buf, num0s)
buf = append(buf, integral...)
}
if trim {
buf = trimTrailingZeroes(buf)
}
return buf
}
var zeroByte = []byte{'0'}
const smallsString = "00010203040506070809" +
"10111213141516171819" +
"20212223242526272829" +
"30313233343536373839" +
"40414243444546474849" +
"50515253545556575859" +
"60616263646566676869" +
"70717273747576777879" +
"80818283848586878889" +
"90919293949596979899"
const maxUint64FormatSize = 20
// formatMantissa formats the mantissa of this decimal into its base10 representation.
// The given buf must be at least 20 characters long to ensure single-word
// mantissas can be formatted in place. If this decimal has a mantissa composed
// by multiple words, the given buf is ignored and the formatted mantissa is
// returned as a new allocation from the `big` package in the stdlib.
func (d *Decimal) formatMantissa(buf []byte) []byte {
var (
us uint
words = d.value.Bits()
i = len(buf)
)
switch len(words) {
case 0:
return zeroByte
case 1:
us = uint(words[0])
default:
// MarshalText cannot fail
buf, _ = d.value.MarshalText()
if buf[0] == '-' {
buf = buf[1:]
}
return buf
}
for us >= 100 {
is := us % 100 * 2
us /= 100
i -= 2
buf[i+1] = smallsString[is+1]
buf[i+0] = smallsString[is+0]
}
// us < 100
is := us * 2
i--
buf[i] = smallsString[is+1]
if us >= 10 {
i--
buf[i] = smallsString[is]
}
return buf[i:]
}
// formatFast formats this decimal number into its base10 representation.
// If round is true, the number will be rounded to the given precision,
// which must be >= 0
// If trim is true, trailing zeroes after the decimal period will be stripped
func (d *Decimal) formatFast(prec int, round bool, trim bool) []byte {
var (
buf []byte
exp int
sign int
short [maxUint64FormatSize]byte
integral = d.formatMantissa(short[:])
)
if prec < 0 {
panic("decimal: formatFast with prec < 0")
}
if round {
// prec is the amount of decimal places after the period we want;
// However, to perform string-based rounding in our integer, we need
// prec to be the total amount of significant digits in the mantissa
// (i.e. the number of integral digits + the number of decimals)
// Let's adjust prec accordingly based on the exponent for the number
// and iprec, which is the precision of our mantissa
iprec := len(integral)
if d.exp > 0 {
prec += int(d.exp) + iprec
} else {
if adj := int(d.exp) + iprec; adj > -prec {
prec += adj
} else {
prec = -prec
}
}
if prec > 0 {
// if prec > 0, perform string-based rounding on the integral to
integral = roundString(integral, prec)
exp = int(d.exp) + iprec - len(integral)
sign = d.value.Sign()
} else if prec < 0 {
integral = nil
prec = -prec
exp = -prec
} else {
integral = zeroByte
}
} else {
exp = int(d.exp)
sign = d.value.Sign()
prec = len(integral)
}
// alloc allocates the destination buf to the right size, and prepends a
// negative sign for negative numbers
alloc := func(length int) []byte {
buf := make([]byte, 0, length+1)
if sign < 0 {
buf = append(buf, '-')
}
return buf
}
// exp > 0, so integral is truly integral but scaled up; there's no period
if exp > 0 {
buf = alloc(len(integral) + exp)
buf = append(buf, integral...)
if sign != 0 {
buf = appendZeroes(buf, exp)
}
return buf
}
const zeroRadix = "0."
switch radix := len(integral) + exp; {
// log10(integral) == scale, so place "0." immediately before integral: 0.123456
case radix == 0:
buf = alloc(len(zeroRadix) + len(integral))
buf = append(buf, zeroRadix...)
buf = append(buf, integral...)
// log10(integral) > scale, so the period is somewhere inside integral: 123.456
case radix > 0:
buf = alloc(len(integral) + 1)
buf = append(buf, integral[:radix]...)
if radix < len(integral) {
buf = append(buf, '.')
buf = append(buf, integral[radix:]...)
} else {
trim = false
}
// log10(integral) < scale, so put "0." and fill with zeroes until integral: 0.00000123456
default:
end := len(integral)
if prec < end {
end = prec
}
buf = alloc(len(zeroRadix) - radix + end)
buf = append(buf, zeroRadix...)
buf = appendZeroes(buf, -radix)
buf = append(buf, integral[:end]...)
}
if trim {
buf = trimTrailingZeroes(buf)
}
return buf
}
// allZeros returns true if every character in b is '0'.
func allZeros(b []byte) bool {
for _, c := range b {
if c != '0' {
return false
}
}
return true
}
// roundString rounds the plain numeric string (e.g., "1234") b.
func roundString(b []byte, prec int) []byte {
if prec >= len(b) {
return appendZeroes(b, prec-len(b))
}
// Trim zeros until prec. This is useful when we can round exactly by simply
// chopping zeros off the end of the number.
if allZeros(b[prec:]) {
return b[:prec]
}
b = b[:prec+1]
i := prec - 1
// Do the rounding away from zero and check if we overflowed;
// if so we'll have to carry
if b[i+1] >= '5' {
b[i]++
}
if b[i] != '9'+1 {
return b[:prec]
}
b[i] = '0'
for i--; i >= 0; i-- {
if b[i] != '9' {
b[i]++
break
}
b[i] = '0'
}
// Carried all the way over to the first column, so slide the buffer down
// instead of reallocating.
if b[0] == '0' {
copy(b[1:], b)
b[0] = '1'
// We might end up with an extra digit of precision. E.g., given the
// decimal 9.9 with a requested precision of 1, we'd convert 99 -> 10.
// Let the calling code handle that case.
prec++
}
return b[:prec]
}