...

Source file src/github.com/cockroachdb/apd/v3/format.go

Documentation: github.com/cockroachdb/apd/v3

     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  
     7  package apd
     8  
     9  import (
    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 (the 'g' format avoids the use of 'f' in this case). All other
    27  // formats always show the exact precision of the Decimal.
    28  func (d *Decimal) Text(format byte) string {
    29  	var buf [16]byte
    30  	return string(d.Append(buf[:0], format))
    31  }
    32  
    33  // String formats x like x.Text('G'). It matches the to-scientific-string
    34  // conversion of the GDA spec.
    35  func (d *Decimal) String() string {
    36  	return d.Text('G')
    37  }
    38  
    39  // lowestZeroNegativeCoefficientCockroach is the smallest co-efficient in
    40  // Cockroach supports using 0E<coefficient>.
    41  const lowestZeroNegativeCoefficientCockroach = -2000
    42  
    43  // Append appends to buf the string form of the decimal number d,
    44  // as generated by d.Text, and returns the extended buffer.
    45  func (d *Decimal) Append(buf []byte, fmtString byte) []byte {
    46  	// sign
    47  	if d.Negative {
    48  		buf = append(buf, '-')
    49  	}
    50  
    51  	switch d.Form {
    52  	case Finite:
    53  		// ignore
    54  	case NaN:
    55  		return append(buf, "NaN"...)
    56  	case NaNSignaling:
    57  		return append(buf, "sNaN"...)
    58  	case Infinite:
    59  		return append(buf, "Infinity"...)
    60  	default:
    61  		return append(buf, "unknown"...)
    62  	}
    63  
    64  	var scratch [16]byte
    65  	digits := d.Coeff.Append(scratch[:0], 10)
    66  	switch fmtString {
    67  	case 'e', 'E':
    68  		return fmtE(buf, fmtString, d, digits)
    69  	case 'f':
    70  		return fmtF(buf, d, digits)
    71  	case 'g', 'G':
    72  		digitLen := len(digits)
    73  		// PG formats all 0s after the decimal point in the 0E-<exponent> case
    74  		// (e.g. 0E-9 should be 0.000000000). With the `adjExponentLimit` below,
    75  		// this does not do that, so for 0 with negative coefficients we pad
    76  		// the digit length.
    77  		// Ref: https://github.com/cockroachdb/cockroach/issues/102217
    78  		//
    79  		// To avoid leaking too much memory for pathological cases, e.g.
    80  		// 0E-100000000, we also fall back to the default exponent format
    81  		// handling when the exponent is below cockroach's lowest supported 0
    82  		// coefficient.
    83  		if d.Coeff.BitLen() == 0 && d.Exponent >= lowestZeroNegativeCoefficientCockroach && d.Exponent < 0 {
    84  			digitLen += int(-d.Exponent)
    85  		}
    86  		// See: http://speleotrove.com/decimal/daconvs.html#reftostr
    87  		const adjExponentLimit = -6
    88  		adj := int(d.Exponent) + (digitLen - 1)
    89  		if d.Exponent <= 0 && adj >= adjExponentLimit {
    90  			return fmtF(buf, d, digits)
    91  		}
    92  		// We need to convert the either g or G into a e or E since that's what fmtE
    93  		// expects. This is indeed fmtString - 2, but attempting to do that in a way that
    94  		// illustrates the intention.
    95  		return fmtE(buf, fmtString+'e'-'g', d, digits)
    96  	}
    97  
    98  	if d.Negative {
    99  		buf = buf[:len(buf)-1] // sign was added prematurely - remove it again
   100  	}
   101  	return append(buf, '%', fmtString)
   102  }
   103  
   104  // %e: d.ddddde±d
   105  func fmtE(buf []byte, fmt byte, d *Decimal, digits []byte) []byte {
   106  	adj := int64(d.Exponent) + int64(len(digits)) - 1
   107  	buf = append(buf, digits[0])
   108  	if len(digits) > 1 {
   109  		buf = append(buf, '.')
   110  		buf = append(buf, digits[1:]...)
   111  	}
   112  	buf = append(buf, fmt)
   113  	var ch byte
   114  	if adj < 0 {
   115  		ch = '-'
   116  		adj = -adj
   117  	} else {
   118  		ch = '+'
   119  	}
   120  	buf = append(buf, ch)
   121  	return strconv.AppendInt(buf, adj, 10)
   122  }
   123  
   124  // %f: ddddddd.ddddd
   125  func fmtF(buf []byte, d *Decimal, digits []byte) []byte {
   126  	if d.Exponent < 0 {
   127  		if left := -int(d.Exponent) - len(digits); left >= 0 {
   128  			buf = append(buf, "0."...)
   129  			for i := 0; i < left; i++ {
   130  				buf = append(buf, '0')
   131  			}
   132  			buf = append(buf, digits...)
   133  		} else if left < 0 {
   134  			offset := -left
   135  			buf = append(buf, digits[:offset]...)
   136  			buf = append(buf, '.')
   137  			buf = append(buf, digits[offset:]...)
   138  		}
   139  	} else if d.Exponent >= 0 {
   140  		buf = append(buf, digits...)
   141  		for i := int32(0); i < d.Exponent; i++ {
   142  			buf = append(buf, '0')
   143  		}
   144  	}
   145  	return buf
   146  }
   147  
   148  var _ fmt.Formatter = decimalZero // *Decimal must implement fmt.Formatter
   149  
   150  // Format implements fmt.Formatter. It accepts many of the regular formats for
   151  // floating-point numbers ('e', 'E', 'f', 'F', 'g', 'G') as well as 's' and 'v',
   152  // which are handled like 'G'. Format also supports the output field width, as
   153  // well as the format flags '+' and ' ' for sign control, '0' for space or zero
   154  // padding, and '-' for left or right justification. It does not support
   155  // precision. See the fmt package for details.
   156  func (d *Decimal) Format(s fmt.State, format rune) {
   157  	switch format {
   158  	case 'e', 'E', 'f', 'g', 'G':
   159  		// nothing to do
   160  	case 'F':
   161  		// (*Decimal).Text doesn't support 'F'; handle like 'f'
   162  		format = 'f'
   163  	case 'v', 's':
   164  		// handle like 'G'
   165  		format = 'G'
   166  	default:
   167  		fmt.Fprintf(s, "%%!%c(*apd.Decimal=%s)", format, d.String())
   168  		return
   169  	}
   170  	var buf []byte
   171  	buf = d.Append(buf, byte(format))
   172  	if len(buf) == 0 {
   173  		buf = []byte("?") // should never happen, but don't crash
   174  	}
   175  	// len(buf) > 0
   176  
   177  	var sign string
   178  	switch {
   179  	case buf[0] == '-':
   180  		sign = "-"
   181  		buf = buf[1:]
   182  	case buf[0] == '+':
   183  		// +Inf
   184  		sign = "+"
   185  		if s.Flag(' ') {
   186  			sign = " "
   187  		}
   188  		buf = buf[1:]
   189  	case s.Flag('+'):
   190  		sign = "+"
   191  	case s.Flag(' '):
   192  		sign = " "
   193  	}
   194  
   195  	var padding int
   196  	if width, hasWidth := s.Width(); hasWidth && width > len(sign)+len(buf) {
   197  		padding = width - len(sign) - len(buf)
   198  	}
   199  
   200  	switch {
   201  	case s.Flag('0') && d.Form == Finite:
   202  		// 0-padding on left
   203  		writeMultiple(s, sign, 1)
   204  		writeMultiple(s, "0", padding)
   205  		s.Write(buf)
   206  	case s.Flag('-'):
   207  		// padding on right
   208  		writeMultiple(s, sign, 1)
   209  		s.Write(buf)
   210  		writeMultiple(s, " ", padding)
   211  	default:
   212  		// padding on left
   213  		writeMultiple(s, " ", padding)
   214  		writeMultiple(s, sign, 1)
   215  		s.Write(buf)
   216  	}
   217  }
   218  
   219  // write count copies of text to s
   220  func writeMultiple(s fmt.State, text string, count int) {
   221  	if len(text) > 0 {
   222  		b := []byte(text)
   223  		for ; count > 0; count-- {
   224  			s.Write(b)
   225  		}
   226  	}
   227  }
   228  

View as plain text