github.com/rsc/tmp@v0.0.0-20240517235954-6deaab19748b/bootstrap/internal/gc/big/decimal.go (about)

     1  // Do not edit. Bootstrap copy of /Users/rsc/g/go/src/cmd/internal/gc/big/decimal.go
     2  
     3  // Copyright 2015 The Go Authors. All rights reserved.
     4  // Use of this source code is governed by a BSD-style
     5  // license that can be found in the LICENSE file.
     6  
     7  // This file implements multi-precision decimal numbers.
     8  // The implementation is for float to decimal conversion only;
     9  // not general purpose use.
    10  // The only operations are precise conversion from binary to
    11  // decimal and rounding.
    12  //
    13  // The key observation and some code (shr) is borrowed from
    14  // strconv/decimal.go: conversion of binary fractional values can be done
    15  // precisely in multi-precision decimal because 2 divides 10 (required for
    16  // >> of mantissa); but conversion of decimal floating-point values cannot
    17  // be done precisely in binary representation.
    18  //
    19  // In contrast to strconv/decimal.go, only right shift is implemented in
    20  // decimal format - left shift can be done precisely in binary format.
    21  
    22  package big
    23  
    24  // A decimal represents a floating-point number in decimal representation.
    25  // The value of a decimal x is x.mant * 10 ** x.exp with 0.5 <= x.mant < 1,
    26  // with the most-significant mantissa digit at index 0.
    27  type decimal struct {
    28  	mant []byte // mantissa ASCII digits, big-endian
    29  	exp  int    // exponent, valid if len(mant) > 0
    30  }
    31  
    32  // Maximum shift amount that can be done in one pass without overflow.
    33  // A Word has _W bits and (1<<maxShift - 1)*10 + 9 must fit into Word.
    34  const maxShift = _W - 4
    35  
    36  // TODO(gri) Since we know the desired decimal precision when converting
    37  // a floating-point number, we may be able to limit the number of decimal
    38  // digits that need to be computed by init by providing an additional
    39  // precision argument and keeping track of when a number was truncated early
    40  // (equivalent of "sticky bit" in binary rounding).
    41  
    42  // TODO(gri) Along the same lines, enforce some limit to shift magnitudes
    43  // to avoid "infinitely" long running conversions (until we run out of space).
    44  
    45  // Init initializes x to the decimal representation of m << shift (for
    46  // shift >= 0), or m >> -shift (for shift < 0).
    47  func (x *decimal) init(m nat, shift int) {
    48  	// special case 0
    49  	if len(m) == 0 {
    50  		x.mant = x.mant[:0]
    51  		return
    52  	}
    53  
    54  	// Optimization: If we need to shift right, first remove any trailing
    55  	// zero bits from m to reduce shift amount that needs to be done in
    56  	// decimal format (since that is likely slower).
    57  	if shift < 0 {
    58  		ntz := m.trailingZeroBits()
    59  		s := uint(-shift)
    60  		if s >= ntz {
    61  			s = ntz // shift at most ntz bits
    62  		}
    63  		m = nat(nil).shr(m, s)
    64  		shift += int(s)
    65  	}
    66  
    67  	// Do any shift left in binary representation.
    68  	if shift > 0 {
    69  		m = nat(nil).shl(m, uint(shift))
    70  		shift = 0
    71  	}
    72  
    73  	// Convert mantissa into decimal representation.
    74  	s := m.decimalString() // TODO(gri) avoid string conversion here
    75  	n := len(s)
    76  	x.exp = n
    77  	// Trim trailing zeros; instead the exponent is tracking
    78  	// the decimal point independent of the number of digits.
    79  	for n > 0 && s[n-1] == '0' {
    80  		n--
    81  	}
    82  	x.mant = append(x.mant[:0], s[:n]...)
    83  
    84  	// Do any (remaining) shift right in decimal representation.
    85  	if shift < 0 {
    86  		for shift < -maxShift {
    87  			shr(x, maxShift)
    88  			shift += maxShift
    89  		}
    90  		shr(x, uint(-shift))
    91  	}
    92  }
    93  
    94  // Possibly optimization: The current implementation of nat.string takes
    95  // a charset argument. When a right shift is needed, we could provide
    96  // "\x00\x01...\x09" instead of "012..9" (as in nat.decimalString) and
    97  // avoid the repeated +'0' and -'0' operations in decimal.shr (and do a
    98  // single +'0' pass at the end).
    99  
   100  // shr implements x >> s, for s <= maxShift.
   101  func shr(x *decimal, s uint) {
   102  	// Division by 1<<s using shift-and-subtract algorithm.
   103  
   104  	// pick up enough leading digits to cover first shift
   105  	r := 0 // read index
   106  	var n Word
   107  	for n>>s == 0 && r < len(x.mant) {
   108  		ch := Word(x.mant[r])
   109  		r++
   110  		n = n*10 + ch - '0'
   111  	}
   112  	if n == 0 {
   113  		// x == 0; shouldn't get here, but handle anyway
   114  		x.mant = x.mant[:0]
   115  		return
   116  	}
   117  	for n>>s == 0 {
   118  		r++
   119  		n *= 10
   120  	}
   121  	x.exp += 1 - r
   122  
   123  	// read a digit, write a digit
   124  	w := 0 // write index
   125  	for r < len(x.mant) {
   126  		ch := Word(x.mant[r])
   127  		r++
   128  		d := n >> s
   129  		n -= d << s
   130  		x.mant[w] = byte(d + '0')
   131  		w++
   132  		n = n*10 + ch - '0'
   133  	}
   134  
   135  	// write extra digits that still fit
   136  	for n > 0 && w < len(x.mant) {
   137  		d := n >> s
   138  		n -= d << s
   139  		x.mant[w] = byte(d + '0')
   140  		w++
   141  		n = n * 10
   142  	}
   143  	x.mant = x.mant[:w] // the number may be shorter (e.g. 1024 >> 10)
   144  
   145  	// append additional digits that didn't fit
   146  	for n > 0 {
   147  		d := n >> s
   148  		n -= d << s
   149  		x.mant = append(x.mant, byte(d+'0'))
   150  		n = n * 10
   151  	}
   152  
   153  	trim(x)
   154  }
   155  
   156  func (x *decimal) String() string {
   157  	if len(x.mant) == 0 {
   158  		return "0"
   159  	}
   160  
   161  	var buf []byte
   162  	switch {
   163  	case x.exp <= 0:
   164  		// 0.00ddd
   165  		buf = append(buf, "0."...)
   166  		buf = appendZeros(buf, -x.exp)
   167  		buf = append(buf, x.mant...)
   168  
   169  	case /* 0 < */ x.exp < len(x.mant):
   170  		// dd.ddd
   171  		buf = append(buf, x.mant[:x.exp]...)
   172  		buf = append(buf, '.')
   173  		buf = append(buf, x.mant[x.exp:]...)
   174  
   175  	default: // len(x.mant) <= x.exp
   176  		// ddd00
   177  		buf = append(buf, x.mant...)
   178  		buf = appendZeros(buf, x.exp-len(x.mant))
   179  	}
   180  
   181  	return string(buf)
   182  }
   183  
   184  // appendZeros appends n 0 digits to buf and returns buf.
   185  func appendZeros(buf []byte, n int) []byte {
   186  	for ; n > 0; n-- {
   187  		buf = append(buf, '0')
   188  	}
   189  	return buf
   190  }
   191  
   192  // shouldRoundUp reports if x should be rounded up
   193  // if shortened to n digits. n must be a valid index
   194  // for x.mant.
   195  func shouldRoundUp(x *decimal, n int) bool {
   196  	if x.mant[n] == '5' && n+1 == len(x.mant) {
   197  		// exactly halfway - round to even
   198  		return n > 0 && (x.mant[n-1]-'0')&1 != 0
   199  	}
   200  	// not halfway - digit tells all (x.mant has no trailing zeros)
   201  	return x.mant[n] >= '5'
   202  }
   203  
   204  // round sets x to (at most) n mantissa digits by rounding it
   205  // to the nearest even value with n (or fever) mantissa digits.
   206  // If n < 0, x remains unchanged.
   207  func (x *decimal) round(n int) {
   208  	if n < 0 || n >= len(x.mant) {
   209  		return // nothing to do
   210  	}
   211  
   212  	if shouldRoundUp(x, n) {
   213  		x.roundUp(n)
   214  	} else {
   215  		x.roundDown(n)
   216  	}
   217  }
   218  
   219  func (x *decimal) roundUp(n int) {
   220  	if n < 0 || n >= len(x.mant) {
   221  		return // nothing to do
   222  	}
   223  	// 0 <= n < len(x.mant)
   224  
   225  	// find first digit < '9'
   226  	for n > 0 && x.mant[n-1] >= '9' {
   227  		n--
   228  	}
   229  
   230  	if n == 0 {
   231  		// all digits are '9's => round up to '1' and update exponent
   232  		x.mant[0] = '1' // ok since len(x.mant) > n
   233  		x.mant = x.mant[:1]
   234  		x.exp++
   235  		return
   236  	}
   237  
   238  	// n > 0 && x.mant[n-1] < '9'
   239  	x.mant[n-1]++
   240  	x.mant = x.mant[:n]
   241  	// x already trimmed
   242  }
   243  
   244  func (x *decimal) roundDown(n int) {
   245  	if n < 0 || n >= len(x.mant) {
   246  		return // nothing to do
   247  	}
   248  	x.mant = x.mant[:n]
   249  	trim(x)
   250  }
   251  
   252  // trim cuts off any trailing zeros from x's mantissa;
   253  // they are meaningless for the value of x.
   254  func trim(x *decimal) {
   255  	i := len(x.mant)
   256  	for i > 0 && x.mant[i-1] == '0' {
   257  		i--
   258  	}
   259  	x.mant = x.mant[:i]
   260  }