github.com/ydb-platform/ydb-go-sdk/v3@v3.57.0/internal/decimal/decimal.go (about)

     1  package decimal
     2  
     3  import (
     4  	"math/big"
     5  	"math/bits"
     6  
     7  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xstring"
     8  )
     9  
    10  const wordSize = bits.UintSize / 8
    11  
    12  var (
    13  	ten  = big.NewInt(10)
    14  	zero = big.NewInt(0)
    15  	one  = big.NewInt(1)
    16  	inf  = big.NewInt(0).Mul(
    17  		big.NewInt(100000000000000000),
    18  		big.NewInt(1000000000000000000),
    19  	)
    20  	nan    = big.NewInt(0).Add(inf, one)
    21  	err    = big.NewInt(0).Add(nan, one)
    22  	neginf = big.NewInt(0).Neg(inf)
    23  	negnan = big.NewInt(0).Neg(nan)
    24  )
    25  
    26  const (
    27  	errorTag = "<error>"
    28  )
    29  
    30  // IsInf reports whether x is an infinity.
    31  func IsInf(x *big.Int) bool { return x.CmpAbs(inf) == 0 }
    32  
    33  // IsNaN reports whether x is a "not-a-number" value.
    34  func IsNaN(x *big.Int) bool { return x.CmpAbs(nan) == 0 }
    35  
    36  // IsErr reports whether x is an "error" value.
    37  func IsErr(x *big.Int) bool { return x.Cmp(err) == 0 }
    38  
    39  // Inf returns infinity value.
    40  func Inf() *big.Int { return big.NewInt(0).Set(inf) }
    41  
    42  // NaN returns "not-a-number" value.
    43  func NaN() *big.Int { return big.NewInt(0).Set(nan) }
    44  
    45  // Err returns "error" value.
    46  func Err() *big.Int { return big.NewInt(0).Set(err) }
    47  
    48  // FromBytes converts bytes representation of decimal to big integer.
    49  // Most callers should use FromInt128().
    50  //
    51  // If given bytes contains value that is greater than given precision it
    52  // returns infinity or negative infinity value accordingly the bytes sign.
    53  func FromBytes(bts []byte, precision, scale uint32) *big.Int {
    54  	v := big.NewInt(0)
    55  	if len(bts) == 0 {
    56  		return v
    57  	}
    58  
    59  	v.SetBytes(bts)
    60  
    61  	neg := bts[0]&0x80 != 0
    62  	if neg {
    63  		// Given bytes contains negative value.
    64  		// Interpret is as two's complement.
    65  		not(v)
    66  		v.Add(v, one)
    67  		v.Neg(v)
    68  	}
    69  	if v.CmpAbs(pow(ten, precision)) >= 0 {
    70  		if neg {
    71  			v.Set(neginf)
    72  		} else {
    73  			v.Set(inf)
    74  		}
    75  	}
    76  
    77  	return v
    78  }
    79  
    80  // FromInt128 returns big integer from given array. That is, it interprets
    81  // 16-byte array as 128-bit integer.
    82  func FromInt128(p [16]byte, precision, scale uint32) *big.Int {
    83  	return FromBytes(p[:], precision, scale)
    84  }
    85  
    86  // Parse interprets a string s with the given precision and scale and returns
    87  // the corresponding big integer.
    88  func Parse(s string, precision, scale uint32) (*big.Int, error) {
    89  	if scale > precision {
    90  		return nil, precisionError(s, precision, scale)
    91  	}
    92  
    93  	v := big.NewInt(0)
    94  	if s == "" {
    95  		return v, nil
    96  	}
    97  
    98  	neg := s[0] == '-'
    99  	if neg || s[0] == '+' {
   100  		s = s[1:]
   101  	}
   102  	if isInf(s) {
   103  		if neg {
   104  			return v.Set(neginf), nil
   105  		}
   106  
   107  		return v.Set(inf), nil
   108  	}
   109  	if isNaN(s) {
   110  		if neg {
   111  			return v.Set(negnan), nil
   112  		}
   113  
   114  		return v.Set(nan), nil
   115  	}
   116  
   117  	integral := precision - scale
   118  
   119  	var dot bool
   120  	for ; len(s) > 0; s = s[1:] {
   121  		c := s[0]
   122  		if c == '.' {
   123  			if dot {
   124  				return nil, syntaxError(s)
   125  			}
   126  			dot = true
   127  
   128  			continue
   129  		}
   130  		if dot {
   131  			if scale > 0 {
   132  				scale--
   133  			} else {
   134  				break
   135  			}
   136  		}
   137  
   138  		if !isDigit(c) {
   139  			return nil, syntaxError(s)
   140  		}
   141  
   142  		v.Mul(v, ten)
   143  		v.Add(v, big.NewInt(int64(c-'0')))
   144  
   145  		if !dot && v.Cmp(zero) > 0 && integral == 0 {
   146  			if neg {
   147  				return neginf, nil
   148  			}
   149  
   150  			return inf, nil
   151  		}
   152  		integral--
   153  	}
   154  	//nolint:nestif
   155  	if len(s) > 0 { // Characters remaining.
   156  		c := s[0]
   157  		if !isDigit(c) {
   158  			return nil, syntaxError(s)
   159  		}
   160  		plus := c > '5'
   161  		if !plus && c == '5' {
   162  			var x big.Int
   163  			plus = x.And(v, one).Cmp(zero) != 0 // Last digit is not a zero.
   164  			for !plus && len(s) > 1 {
   165  				s = s[1:]
   166  				c := s[0]
   167  				if !isDigit(c) {
   168  					return nil, syntaxError(s)
   169  				}
   170  				plus = c != '0'
   171  			}
   172  		}
   173  		if plus {
   174  			v.Add(v, one)
   175  			if v.Cmp(pow(ten, precision)) >= 0 {
   176  				v.Set(inf)
   177  			}
   178  		}
   179  	}
   180  	v.Mul(v, pow(ten, scale))
   181  	if neg {
   182  		v.Neg(v)
   183  	}
   184  
   185  	return v, nil
   186  }
   187  
   188  // Format returns the string representation of x with the given precision and
   189  // scale.
   190  func Format(x *big.Int, precision, scale uint32) string {
   191  	switch {
   192  	case x.CmpAbs(inf) == 0:
   193  		if x.Sign() < 0 {
   194  			return "-inf"
   195  		}
   196  
   197  		return "inf"
   198  
   199  	case x.CmpAbs(nan) == 0:
   200  		if x.Sign() < 0 {
   201  			return "-nan"
   202  		}
   203  
   204  		return "nan"
   205  
   206  	case x == nil:
   207  		return "0"
   208  	}
   209  
   210  	v := big.NewInt(0).Set(x)
   211  	neg := x.Sign() < 0
   212  	if neg {
   213  		// Convert negative to positive.
   214  		v.Neg(x)
   215  	}
   216  
   217  	// log_{10}(2^120) ~= 36.12, 37 decimal places
   218  	// plus dot, zero before dot, sign.
   219  	bts := make([]byte, 40)
   220  	pos := len(bts)
   221  
   222  	var digit big.Int
   223  	for ; v.Cmp(zero) > 0; v.Div(v, ten) {
   224  		if precision == 0 {
   225  			return errorTag
   226  		}
   227  		precision--
   228  
   229  		digit.Mod(v, ten)
   230  		d := int(digit.Int64())
   231  		if d != 0 || scale == 0 || pos > 0 {
   232  			const numbers = "0123456789"
   233  			pos--
   234  			bts[pos] = numbers[d]
   235  		}
   236  		if scale > 0 {
   237  			scale--
   238  			if scale == 0 && pos > 0 {
   239  				pos--
   240  				bts[pos] = '.'
   241  			}
   242  		}
   243  	}
   244  	if scale > 0 {
   245  		for ; scale > 0; scale-- {
   246  			if precision == 0 {
   247  				return errorTag
   248  			}
   249  			precision--
   250  			pos--
   251  			bts[pos] = '0'
   252  		}
   253  
   254  		pos--
   255  		bts[pos] = '.'
   256  	}
   257  	if bts[pos] == '.' {
   258  		pos--
   259  		bts[pos] = '0'
   260  	}
   261  	if neg {
   262  		pos--
   263  		bts[pos] = '-'
   264  	}
   265  
   266  	return xstring.FromBytes(bts[pos:])
   267  }
   268  
   269  // BigIntToByte returns the 16-byte array representation of x.
   270  //
   271  // If x value does not fit in 16 bytes with given precision, it returns 16-byte
   272  // representation of infinity or negative infinity value accordingly to x's sign.
   273  func BigIntToByte(x *big.Int, precision, scale uint32) (p [16]byte) {
   274  	if !IsInf(x) && !IsNaN(x) && !IsErr(x) && x.CmpAbs(pow(ten, precision)) >= 0 {
   275  		if x.Sign() < 0 {
   276  			x = neginf
   277  		} else {
   278  			x = inf
   279  		}
   280  	}
   281  	put(x, p[:])
   282  
   283  	return p
   284  }
   285  
   286  func put(x *big.Int, p []byte) {
   287  	neg := x.Sign() < 0
   288  	if neg {
   289  		x = complement(x)
   290  	}
   291  	i := len(p)
   292  	for _, d := range x.Bits() {
   293  		for j := 0; j < wordSize; j++ {
   294  			i--
   295  			p[i] = byte(d)
   296  			d >>= 8
   297  		}
   298  	}
   299  	var pad byte
   300  	if neg {
   301  		pad = 0xff
   302  	}
   303  	for 0 < i && i < len(p) {
   304  		i--
   305  		p[i] = pad
   306  	}
   307  }
   308  
   309  func Append(p []byte, x *big.Int) []byte {
   310  	n := len(p)
   311  	p = ensure(p, size(x))
   312  	put(x, p[n:])
   313  
   314  	return p
   315  }
   316  
   317  func size(x *big.Int) int {
   318  	if x.Sign() < 0 {
   319  		x = complement(x)
   320  	}
   321  
   322  	return len(x.Bits()) * wordSize
   323  }
   324  
   325  func ensure(p []byte, n int) []byte {
   326  	var (
   327  		l = len(p)
   328  		c = cap(p)
   329  	)
   330  	if c-l < n {
   331  		cp := make([]byte, l+n)
   332  		copy(cp, p)
   333  		p = cp
   334  	}
   335  
   336  	return p[:l+n]
   337  }
   338  
   339  // not is almost the same as x.Not() but without handling the sign of x.
   340  // That is, it more similar to x.Xor(ones) where ones is x bits all set to 1.
   341  func not(x *big.Int) {
   342  	abs := x.Bits()
   343  	for i, d := range abs {
   344  		abs[i] = ^d
   345  	}
   346  }
   347  
   348  // pow returns new instance of big.Int equal to x^n.
   349  func pow(x *big.Int, n uint32) *big.Int {
   350  	var (
   351  		v = big.NewInt(1)
   352  		m = big.NewInt(0).Set(x)
   353  	)
   354  	for n > 0 {
   355  		if n&1 != 0 {
   356  			v.Mul(v, m)
   357  		}
   358  		n >>= 1
   359  		m.Mul(m, m)
   360  	}
   361  
   362  	return v
   363  }
   364  
   365  // complement returns two's complement of x.
   366  // x must be negative.
   367  func complement(x *big.Int) *big.Int {
   368  	x = big.NewInt(0).Set(x)
   369  	not(x)
   370  	x.Neg(x)
   371  	x.Add(x, one)
   372  
   373  	return x
   374  }
   375  
   376  func isInf(s string) bool {
   377  	return len(s) >= 3 && (s[0] == 'i' || s[0] == 'I') && (s[1] == 'n' || s[1] == 'N') && (s[2] == 'f' || s[2] == 'F')
   378  }
   379  
   380  func isNaN(s string) bool {
   381  	return len(s) >= 3 && (s[0] == 'n' || s[0] == 'N') && (s[1] == 'a' || s[1] == 'A') && (s[2] == 'n' || s[2] == 'N')
   382  }
   383  
   384  func isDigit(c byte) bool {
   385  	return '0' <= c && c <= '9'
   386  }