1// Copyright 2015 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Adapted from math/big/ftoa.go.
6
7package apd
8
9import (
10	"fmt"
11	"strconv"
12)
13
14// Text converts the floating-point number x to a string according
15// to the given format. The format is one of:
16//
17//	'e'	-d.dddde±dd, decimal exponent, exponent digits
18//	'E'	-d.ddddE±dd, decimal exponent, exponent digits
19//	'f'	-ddddd.dddd, no exponent
20//	'g'	like 'e' for large exponents, like 'f' otherwise
21//	'G'	like 'E' for large exponents, like 'f' otherwise
22//
23// If format is a different character, Text returns a "%" followed by the
24// unrecognized.Format character. The 'f' format has the possibility of
25// displaying precision that is not present in the Decimal when it appends
26// zeros. All other formats always show the exact precision of the Decimal.
27func (d *Decimal) Text(format byte) string {
28	cap := 10 // TODO(gri) determine a good/better value here
29	return string(d.Append(make([]byte, 0, cap), format))
30}
31
32// String formats x like x.Text('G'). It matches the to-scientific-string
33// conversion of the GDA spec.
34func (d *Decimal) String() string {
35	return d.Text('G')
36}
37
38// Append appends to buf the string form of the decimal number d,
39// as generated by d.Text, and returns the extended buffer.
40func (d *Decimal) Append(buf []byte, fmt byte) []byte {
41	// sign
42	if d.Negative {
43		buf = append(buf, '-')
44	}
45
46	switch d.Form {
47	case Finite:
48		// ignore
49	case NaN:
50		return append(buf, "NaN"...)
51	case NaNSignaling:
52		return append(buf, "sNaN"...)
53	case Infinite:
54		return append(buf, "Infinity"...)
55	default:
56		return append(buf, "unknown"...)
57	}
58
59	digits := d.Coeff.String()
60	switch fmt {
61	case 'e', 'E':
62		return fmtE(buf, fmt, d, digits)
63	case 'f':
64		return fmtF(buf, d, digits)
65	case 'g', 'G':
66		// See: http://speleotrove.com/decimal/daconvs.html#reftostr
67		const adjExponentLimit = -6
68		adj := int(d.Exponent) + (len(digits) - 1)
69		if d.Exponent <= 0 && adj >= adjExponentLimit {
70			return fmtF(buf, d, digits)
71		}
72		// We need to convert the either g or G into a e or E since that's what fmtE
73		// expects. This is indeed fmt - 2, but attempting to do that in a way that
74		// illustrates the intention.
75		return fmtE(buf, fmt+'e'-'g', d, digits)
76	}
77
78	if d.Negative {
79		buf = buf[:len(buf)-1] // sign was added prematurely - remove it again
80	}
81	return append(buf, '%', fmt)
82}
83
84// %e: d.ddddde±d
85func fmtE(buf []byte, fmt byte, d *Decimal, digits string) []byte {
86	adj := int64(d.Exponent) + int64(len(digits)) - 1
87	buf = append(buf, digits[0])
88	if len(digits) > 1 {
89		buf = append(buf, '.')
90		buf = append(buf, digits[1:]...)
91	}
92	buf = append(buf, fmt)
93	var ch byte
94	if adj < 0 {
95		ch = '-'
96		adj = -adj
97	} else {
98		ch = '+'
99	}
100	buf = append(buf, ch)
101	return strconv.AppendInt(buf, adj, 10)
102}
103
104// %f: ddddddd.ddddd
105func fmtF(buf []byte, d *Decimal, digits string) []byte {
106	if d.Exponent < 0 {
107		if left := -int(d.Exponent) - len(digits); left >= 0 {
108			buf = append(buf, "0."...)
109			for i := 0; i < left; i++ {
110				buf = append(buf, '0')
111			}
112			buf = append(buf, digits...)
113		} else if left < 0 {
114			offset := -left
115			buf = append(buf, digits[:offset]...)
116			buf = append(buf, '.')
117			buf = append(buf, digits[offset:]...)
118		}
119	} else if d.Exponent >= 0 {
120		buf = append(buf, digits...)
121		for i := int32(0); i < d.Exponent; i++ {
122			buf = append(buf, '0')
123		}
124	}
125	return buf
126}
127
128var _ fmt.Formatter = decimalZero // *Decimal must implement fmt.Formatter
129
130// Format implements fmt.Formatter. It accepts many of the regular formats for
131// floating-point numbers ('e', 'E', 'f', 'F', 'g', 'G') as well as 's' and 'v',
132// which are handled like 'G'. Format also supports the output field width, as
133// well as the format flags '+' and ' ' for sign control, '0' for space or zero
134// padding, and '-' for left or right justification. It does not support
135// precision. See the fmt package for details.
136func (d *Decimal) Format(s fmt.State, format rune) {
137	switch format {
138	case 'e', 'E', 'f', 'g', 'G':
139		// nothing to do
140	case 'F':
141		// (*Decimal).Text doesn't support 'F'; handle like 'f'
142		format = 'f'
143	case 'v', 's':
144		// handle like 'G'
145		format = 'G'
146	default:
147		fmt.Fprintf(s, "%%!%c(*apd.Decimal=%s)", format, d.String())
148		return
149	}
150	var buf []byte
151	buf = d.Append(buf, byte(format))
152	if len(buf) == 0 {
153		buf = []byte("?") // should never happen, but don't crash
154	}
155	// len(buf) > 0
156
157	var sign string
158	switch {
159	case buf[0] == '-':
160		sign = "-"
161		buf = buf[1:]
162	case buf[0] == '+':
163		// +Inf
164		sign = "+"
165		if s.Flag(' ') {
166			sign = " "
167		}
168		buf = buf[1:]
169	case s.Flag('+'):
170		sign = "+"
171	case s.Flag(' '):
172		sign = " "
173	}
174
175	var padding int
176	if width, hasWidth := s.Width(); hasWidth && width > len(sign)+len(buf) {
177		padding = width - len(sign) - len(buf)
178	}
179
180	switch {
181	case s.Flag('0') && d.Form == Finite:
182		// 0-padding on left
183		writeMultiple(s, sign, 1)
184		writeMultiple(s, "0", padding)
185		s.Write(buf)
186	case s.Flag('-'):
187		// padding on right
188		writeMultiple(s, sign, 1)
189		s.Write(buf)
190		writeMultiple(s, " ", padding)
191	default:
192		// padding on left
193		writeMultiple(s, " ", padding)
194		writeMultiple(s, sign, 1)
195		s.Write(buf)
196	}
197}
198
199// write count copies of text to s
200func writeMultiple(s fmt.State, text string, count int) {
201	if len(text) > 0 {
202		b := []byte(text)
203		for ; count > 0; count-- {
204			s.Write(b)
205		}
206	}
207}
208