-
Notifications
You must be signed in to change notification settings - Fork 37
/
format.go
227 lines (209 loc) · 6.2 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
// Copyright 2015 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.
// Adapted from math/big/ftoa.go.
package apd
import (
"fmt"
"strconv"
)
// Text converts the floating-point number x to a string according
// to the given format. The format is one of:
//
// 'e' -d.dddde±dd, decimal exponent, exponent digits
// 'E' -d.ddddE±dd, decimal exponent, exponent digits
// 'f' -ddddd.dddd, no exponent
// 'g' like 'e' for large exponents, like 'f' otherwise
// 'G' like 'E' for large exponents, like 'f' otherwise
//
// If format is a different character, Text returns a "%" followed by the
// unrecognized.Format character. The 'f' format has the possibility of
// displaying precision that is not present in the Decimal when it appends
// zeros (the 'g' format avoids the use of 'f' in this case). All other
// formats always show the exact precision of the Decimal.
func (d *Decimal) Text(format byte) string {
var buf [16]byte
return string(d.Append(buf[:0], format))
}
// String formats x like x.Text('G'). It matches the to-scientific-string
// conversion of the GDA spec.
func (d *Decimal) String() string {
return d.Text('G')
}
// lowestZeroNegativeCoefficientCockroach is the smallest co-efficient in
// Cockroach supports using 0E<coefficient>.
const lowestZeroNegativeCoefficientCockroach = -2000
// Append appends to buf the string form of the decimal number d,
// as generated by d.Text, and returns the extended buffer.
func (d *Decimal) Append(buf []byte, fmtString byte) []byte {
// sign
if d.Negative {
buf = append(buf, '-')
}
switch d.Form {
case Finite:
// ignore
case NaN:
return append(buf, "NaN"...)
case NaNSignaling:
return append(buf, "sNaN"...)
case Infinite:
return append(buf, "Infinity"...)
default:
return append(buf, "unknown"...)
}
var scratch [16]byte
digits := d.Coeff.Append(scratch[:0], 10)
switch fmtString {
case 'e', 'E':
return fmtE(buf, fmtString, d, digits)
case 'f':
return fmtF(buf, d, digits)
case 'g', 'G':
digitLen := len(digits)
// PG formats all 0s after the decimal point in the 0E-<exponent> case
// (e.g. 0E-9 should be 0.000000000). With the `adjExponentLimit` below,
// this does not do that, so for 0 with negative coefficients we pad
// the digit length.
// Ref: https://github.com/cockroachdb/cockroach/issues/102217
//
// To avoid leaking too much memory for pathological cases, e.g.
// 0E-100000000, we also fall back to the default exponent format
// handling when the exponent is below cockroach's lowest supported 0
// coefficient.
if d.Coeff.BitLen() == 0 && d.Exponent >= lowestZeroNegativeCoefficientCockroach && d.Exponent < 0 {
digitLen += int(-d.Exponent)
}
// See: http://speleotrove.com/decimal/daconvs.html#reftostr
const adjExponentLimit = -6
adj := int(d.Exponent) + (digitLen - 1)
if d.Exponent <= 0 && adj >= adjExponentLimit {
return fmtF(buf, d, digits)
}
// We need to convert the either g or G into a e or E since that's what fmtE
// expects. This is indeed fmtString - 2, but attempting to do that in a way that
// illustrates the intention.
return fmtE(buf, fmtString+'e'-'g', d, digits)
}
if d.Negative {
buf = buf[:len(buf)-1] // sign was added prematurely - remove it again
}
return append(buf, '%', fmtString)
}
// %e: d.ddddde±d
func fmtE(buf []byte, fmt byte, d *Decimal, digits []byte) []byte {
adj := int64(d.Exponent) + int64(len(digits)) - 1
buf = append(buf, digits[0])
if len(digits) > 1 {
buf = append(buf, '.')
buf = append(buf, digits[1:]...)
}
buf = append(buf, fmt)
var ch byte
if adj < 0 {
ch = '-'
adj = -adj
} else {
ch = '+'
}
buf = append(buf, ch)
return strconv.AppendInt(buf, adj, 10)
}
// %f: ddddddd.ddddd
func fmtF(buf []byte, d *Decimal, digits []byte) []byte {
if d.Exponent < 0 {
if left := -int(d.Exponent) - len(digits); left >= 0 {
buf = append(buf, "0."...)
for i := 0; i < left; i++ {
buf = append(buf, '0')
}
buf = append(buf, digits...)
} else if left < 0 {
offset := -left
buf = append(buf, digits[:offset]...)
buf = append(buf, '.')
buf = append(buf, digits[offset:]...)
}
} else if d.Exponent >= 0 {
buf = append(buf, digits...)
for i := int32(0); i < d.Exponent; i++ {
buf = append(buf, '0')
}
}
return buf
}
var _ fmt.Formatter = decimalZero // *Decimal must implement fmt.Formatter
// Format implements fmt.Formatter. It accepts many of the regular formats for
// floating-point numbers ('e', 'E', 'f', 'F', 'g', 'G') as well as 's' and 'v',
// which are handled like 'G'. Format also supports the output field width, as
// well as the format flags '+' and ' ' for sign control, '0' for space or zero
// padding, and '-' for left or right justification. It does not support
// precision. See the fmt package for details.
func (d *Decimal) Format(s fmt.State, format rune) {
switch format {
case 'e', 'E', 'f', 'g', 'G':
// nothing to do
case 'F':
// (*Decimal).Text doesn't support 'F'; handle like 'f'
format = 'f'
case 'v', 's':
// handle like 'G'
format = 'G'
default:
fmt.Fprintf(s, "%%!%c(*apd.Decimal=%s)", format, d.String())
return
}
var buf []byte
buf = d.Append(buf, byte(format))
if len(buf) == 0 {
buf = []byte("?") // should never happen, but don't crash
}
// len(buf) > 0
var sign string
switch {
case buf[0] == '-':
sign = "-"
buf = buf[1:]
case buf[0] == '+':
// +Inf
sign = "+"
if s.Flag(' ') {
sign = " "
}
buf = buf[1:]
case s.Flag('+'):
sign = "+"
case s.Flag(' '):
sign = " "
}
var padding int
if width, hasWidth := s.Width(); hasWidth && width > len(sign)+len(buf) {
padding = width - len(sign) - len(buf)
}
switch {
case s.Flag('0') && d.Form == Finite:
// 0-padding on left
writeMultiple(s, sign, 1)
writeMultiple(s, "0", padding)
s.Write(buf)
case s.Flag('-'):
// padding on right
writeMultiple(s, sign, 1)
s.Write(buf)
writeMultiple(s, " ", padding)
default:
// padding on left
writeMultiple(s, " ", padding)
writeMultiple(s, sign, 1)
s.Write(buf)
}
}
// write count copies of text to s
func writeMultiple(s fmt.State, text string, count int) {
if len(text) > 0 {
b := []byte(text)
for ; count > 0; count-- {
s.Write(b)
}
}
}