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