Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 75 additions & 67 deletions sei-cosmos/types/decimal.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,18 @@
// number of decimal places
Precision = 18

// bits required to represent the above precision
// Ceiling[Log2[10^Precision - 1]]
// bits required to represent the fractional precision.
// DecimalPrecisionBits = ceil(log2(10^Precision) - 1) = 60 for Precision=18.
DecimalPrecisionBits = 60

// decimalTruncateBits is the minimum number of bits removed
// by a truncate operation. It is equal to
// Floor[Log2[10^Precision - 1]].
decimalTruncateBits = DecimalPrecisionBits - 1
// intBitLen is the bit length used by sdk.Int (currently 256-bit)
intBitLen = 256

maxDecBitLen = maxBitLen + decimalTruncateBits
// maxDecBitLen is the maximum allowed bit length for Dec values.
// It is derived instead of hard-coded so that future changes to Precision
// or intBitLen automatically propagate.
// Example with current constants: 256 + 60 − 1 = 315.
maxDecBitLen = intBitLen + DecimalPrecisionBits - 1

// max number of iterations in ApproxRoot function
maxApproxRootIterations = 100
Expand Down Expand Up @@ -109,9 +111,11 @@
// create a new Dec from big integer assuming whole numbers
// CONTRACT: prec <= Precision
func NewDecFromBigIntWithPrec(i *big.Int, prec int64) Dec {
return Dec{
result := Dec{
new(big.Int).Mul(i, precisionMultiplier(prec)),
}
result.assertInValidRange()
return result
}

// create a new Dec from big integer assuming whole numbers
Expand Down Expand Up @@ -187,14 +191,16 @@
if !ok {
return Dec{}, fmt.Errorf("failed to set decimal string with base 10: %s", combinedStr)
}
if combined.BitLen() > maxDecBitLen {
return Dec{}, fmt.Errorf("decimal '%s' out of range; bitLen: got %d, max %d", str, combined.BitLen(), maxDecBitLen)
}
if neg {
combined = new(big.Int).Neg(combined)
}

return Dec{combined}, nil
d := Dec{combined}
if !d.IsInValidRange() {
return Dec{}, fmt.Errorf("decimal '%s' out of range", str)
}

return d, nil
}

// Decimal from string, panic on error
Expand Down Expand Up @@ -231,63 +237,51 @@
// addition
func (d Dec) Add(d2 Dec) Dec {
res := new(big.Int).Add(d.i, d2.i)

if res.BitLen() > maxDecBitLen {
panic("Int overflow")
}
return Dec{res}
result := Dec{res}
result.assertInValidRange()
return result
}

// subtraction
func (d Dec) Sub(d2 Dec) Dec {
res := new(big.Int).Sub(d.i, d2.i)

if res.BitLen() > maxDecBitLen {
panic("Int overflow")
}
return Dec{res}
result := Dec{res}
result.assertInValidRange()
return result
}

// multiplication
func (d Dec) Mul(d2 Dec) Dec {
mul := new(big.Int).Mul(d.i, d2.i)
chopped := chopPrecisionAndRound(mul)

if chopped.BitLen() > maxDecBitLen {
panic("Int overflow")
}
return Dec{chopped}
result := Dec{chopped}
result.assertInValidRange()
return result
}

// multiplication truncate
func (d Dec) MulTruncate(d2 Dec) Dec {
mul := new(big.Int).Mul(d.i, d2.i)
chopped := chopPrecisionAndTruncate(mul)

if chopped.BitLen() > maxDecBitLen {
panic("Int overflow")
}
return Dec{chopped}
result := Dec{chopped}
result.assertInValidRange()
return result
}

// multiplication
func (d Dec) MulInt(i Int) Dec {
mul := new(big.Int).Mul(d.i, i.i)

if mul.BitLen() > maxDecBitLen {
panic("Int overflow")
}
return Dec{mul}
result := Dec{mul}
result.assertInValidRange()
return result
}

// MulInt64 - multiplication with int64
func (d Dec) MulInt64(i int64) Dec {
mul := new(big.Int).Mul(d.i, big.NewInt(i))

if mul.BitLen() > maxDecBitLen {
panic("Int overflow")
}
return Dec{mul}
result := Dec{mul}
result.assertInValidRange()
return result
}

// quotient
Expand All @@ -298,11 +292,9 @@

quo := new(big.Int).Quo(mul, d2.i)
chopped := chopPrecisionAndRound(quo)

if chopped.BitLen() > maxDecBitLen {
panic("Int overflow")
}
return Dec{chopped}
result := Dec{chopped}
result.assertInValidRange()
return result
}

// quotient truncate
Expand All @@ -313,11 +305,9 @@

quo := mul.Quo(mul, d2.i)
chopped := chopPrecisionAndTruncate(quo)

if chopped.BitLen() > maxDecBitLen {
panic("Int overflow")
}
return Dec{chopped}
result := Dec{chopped}
result.assertInValidRange()
return result
}

// quotient, round up
Expand All @@ -328,11 +318,9 @@

quo := new(big.Int).Quo(mul, d2.i)
chopped := chopPrecisionAndRoundUp(quo)

if chopped.BitLen() > maxDecBitLen {
panic("Int overflow")
}
return Dec{chopped}
result := Dec{chopped}
result.assertInValidRange()
return result
}

// quotient
Expand Down Expand Up @@ -620,15 +608,17 @@
quo, rem = quo.QuoRem(tmp, precisionReuse, rem)

// no need to round with a zero remainder regardless of sign
if rem.Cmp(zeroInt) == 0 {
return NewDecFromBigInt(quo)
}

if rem.Sign() == -1 {
return NewDecFromBigInt(quo)
var r Dec
switch rem.Sign() {
case 0:
r = NewDecFromBigInt(quo)
case -1:
r = NewDecFromBigInt(quo)
default:
r = NewDecFromBigInt(quo.Add(quo, oneInt))
}

return NewDecFromBigInt(quo.Add(quo, oneInt))
r.assertInValidRange()
return r
}

// MaxSortableDec is the largest Dec that can be passed into SortableDecBytes()
Expand Down Expand Up @@ -752,8 +742,8 @@
return err
}

if d.i.BitLen() > maxDecBitLen {
return fmt.Errorf("decimal out of range; got: %d, max: %d", d.i.BitLen(), maxDecBitLen)
if !d.IsInValidRange() {
return errors.New("decimal out of range")
}

return nil
Expand Down Expand Up @@ -805,6 +795,24 @@
return d1
}

// IsInValidRange returns true if the decimal's underlying big.Int is within the valid range.
func (d Dec) IsInValidRange() bool {
if d.i == nil {
return true
}
// Use maxDecBitLen (315 bits) to align with the official Cosmos SDK implementation.
// 315 bits can cover all values within (2^256−1)×10^18 − 1,
// so bitLen ≤ maxDecBitLen ensures alignment with the 256-bit boundary of sdk.Int while also supporting 18-decimal-place precision.
return d.i.BitLen() <= maxDecBitLen
}

// assertInValidRange panics if the decimal is out of the valid range
func (d Dec) assertInValidRange() {
if !d.IsInValidRange() {
panic("decimal out of range")

Check warning

Code scanning / CodeQL

Panic in BeginBock or EndBlock consensus methods Warning

Possible panics in BeginBock- or EndBlock-related consensus methods could cause a chain halt
}
}

// intended to be used with require/assert: require.True(DecEq(...))
func DecEq(t *testing.T, exp, got Dec) (*testing.T, bool, string, string, string) {
return t, exp.Equal(got), "expected:\t%v\ngot:\t\t%v", exp.String(), got.String()
Expand Down
Loading
Loading