chore: set math to main (#23697)

This commit is contained in:
Tyler 2025-02-14 09:05:23 -08:00 committed by GitHub
parent 8abca32b6c
commit edcb427a6e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 5911 additions and 1657 deletions

View File

@ -36,10 +36,79 @@ Ref: https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.j
## [Unreleased]
## [math/v1.5.0](https://github.com/cosmos/cosmos-sdk/releases/tag/math/v1.5.0) - 2025-01-06
* [#11783](https://github.com/cosmos/cosmos-sdk/issues/11783) Upstream GDA based decimal type
## [math/v1.4.0](https://github.com/cosmos/cosmos-sdk/releases/tag/math/v1.4.0) - 2024-11-20
### Features
* [#20034](https://github.com/cosmos/cosmos-sdk/pull/20034) Significantly speedup LegacyDec.QuoTruncate and LegacyDec.QuoRoundUp.
### Bug fixes
* Fix [ASA-2024-010: Math](https://github.com/cosmos/cosmos-sdk/security/advisories/GHSA-7225-m954-23v7) Bit length differences between Int and Dec
## [math/v1.3.0](https://github.com/cosmos/cosmos-sdk/releases/tag/math/v1.3.0) - 2024-02-22
### Features
* [#18552](https://github.com/cosmos/cosmos-sdk/pull/18552) Add safe arithmetic operations for `math.Int` that return an error in case of an overflow or any mishap.
* [#18421](https://github.com/cosmos/cosmos-sdk/pull/18421) Add mutative api for `LegacyDec.BigInt()`.
* [#18874](https://github.com/cosmos/cosmos-sdk/pull/18874) Speedup `math.Int.Mul` by removing a duplicate overflow check
* [#19386](https://github.com/cosmos/cosmos-sdk/pull/19386) Speedup `math.Int` overflow checks
* [#19466](https://github.com/cosmos/cosmos-sdk/pull/19466) Speedup `math.NewLegacyDec` by one heap allocation
* [#19467](https://github.com/cosmos/cosmos-sdk/pull/19467) Slightly speedup `math.LegacyDec` `Ceil` and `MarshalTo` methods
* [#19479](https://github.com/cosmos/cosmos-sdk/pull/19479) Speedup `math.LegacyDec` functions that involve `math.Int` by removing a heap allocation. (`Ceil`, `TruncateInt`, `NewLegacyDecFromInt`)
### Bug Fixes
* [#16266](https://github.com/cosmos/cosmos-sdk/pull/16266) fix: legacy dec power mut zero exponent precision.
* [#18519](https://github.com/cosmos/cosmos-sdk/pull/18519) Prevent Overflow in `Dec.Ceil()`.
## [math/v1.2.0](https://github.com/cosmos/cosmos-sdk/releases/tag/math/v1.2.0) - 2023-11-07
### Features
* [#18247](https://github.com/cosmos/cosmos-sdk/pull/18247) Add mutative api for `Uint.BigInt()`.
* [#17803](https://github.com/cosmos/cosmos-sdk/pull/17803) Add mutative api for `Int.BigInt()`.
* [#18030](https://github.com/cosmos/cosmos-sdk/pull/18030) Add mutative api for `NewIntFromBigInt`.
### Bug Fixes
* [#18228](https://github.com/cosmos/cosmos-sdk/pull/18228) Fix panic when calling `BigInt()` on an uninitialized `Uint`.
* [#18214](https://github.com/cosmos/cosmos-sdk/pull/18214) Ensure that modifying the argument to `NewUIntFromBigInt` doesn't mutate the returned value.
* [#18211](https://github.com/cosmos/cosmos-sdk/pull/18211) RelativePow now returns 1 when 0^0, before it was returning the scale factor.
* [#17725](https://github.com/cosmos/cosmos-sdk/pull/17725) Fix state break in ApproxRoot. This has been present since math/v1.0.1. It changed the rounding behavior at precision end in an intermediary division from banker's to truncation. The truncation occurs from binary right shift in the case of square roots. The change is now reverted back to banker's rounding universally for any root.
## [math/v1.1.2](https://github.com/cosmos/cosmos-sdk/releases/tag/math/v1.1.2) - 2023-08-21
### Bug Fixes
* [#17489](https://github.com/cosmos/cosmos-sdk/pull/17489) Revert [#16263](https://github.com/cosmos/cosmos-sdk/pull/16263).
## [math/v1.1.1](https://github.com/cosmos/cosmos-sdk/releases/tag/math/v1.1.1) - 2023-08-21
### Bug Fixes
* [#17480](https://github.com/cosmos/cosmos-sdk/pull/17480) Fix panic when calling `.Size()` on a nil `math.Int` value.
## [math/v1.1.0](https://github.com/cosmos/cosmos-sdk/releases/tag/math/v1.1.0) - 2023-08-19
### Features
* [#17427](https://github.com/cosmos/cosmos-sdk/pull/17427) Implement LegacyDec.MulRoundUp that rounds up at precision end.
### Improvements
* [#17109](https://github.com/cosmos/cosmos-sdk/pull/17109) Add `.ToLegacyDec()` method on `math.Int` type for converting to `math.LegacyDec`.
* [#16263](https://github.com/cosmos/cosmos-sdk/pull/16263) Improved `math/Int.Size` by computing the decimal digits count instead of firstly invoking .Marshal() then checking the length
### Bug Fixes
* [#17352](https://github.com/cosmos/cosmos-sdk/pull/17352) Ensure that modifying the argument to `NewIntFromBigInt` doesn't mutate the returned value.
* [#16266](https://github.com/cosmos/cosmos-sdk/pull/16266) Fix legacy dec power mut zero exponent precision.
## [math/v1.0.1](https://github.com/cosmos/cosmos-sdk/releases/tag/math/v1.0.1) - 2023-05-15

File diff suppressed because it is too large Load Diff

353
math/dec_bench_test.go Normal file
View File

@ -0,0 +1,353 @@
package math
import (
"testing"
"github.com/stretchr/testify/require"
)
func BenchmarkCompareLegacyDecAndNewDecQuotient(b *testing.B) {
specs := map[string]struct {
dividend, divisor string
}{
"small/ small": {
dividend: "100", divisor: "5",
},
"big18/ small": {
dividend: "999999999999999999", divisor: "10",
},
"self18/ self18": {
dividend: "999999999999999999", divisor: "999999999999999999",
},
"big18/ big18": {
dividend: "888888888888888888", divisor: "444444444444444444",
},
"decimal18b/ decimal18c": {
dividend: "8.88888888888888888", divisor: "4.1234567890123",
},
"small/ big18": {
dividend: "100", divisor: "999999999999999999",
},
"big34/ big34": {
dividend: "9999999999999999999999999999999999", divisor: "1999999999999999999999999999999999",
},
"negative big34": {
dividend: "-9999999999999999999999999999999999", divisor: "999999999999999999999999999",
},
"decimal small": {
dividend: "0.0000000001", divisor: "10",
},
"decimal small/decimal small ": {
dividend: "0.0000000001", divisor: "0.0001",
},
}
for name, spec := range specs {
b.Run(name, func(b *testing.B) {
b.Run("LegacyDec", func(b *testing.B) {
dv, ds := LegacyMustNewDecFromStr(spec.dividend), LegacyMustNewDecFromStr(spec.divisor)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = dv.Quo(ds)
}
})
b.Run("NewDec", func(b *testing.B) {
dv, ds := must(NewDecFromString(spec.dividend)), must(NewDecFromString(spec.divisor))
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = dv.Quo(ds)
}
})
})
}
}
func BenchmarkCompareLegacyDecAndNewDecSum(b *testing.B) {
specs := map[string]struct {
summands []string
}{
"1+2": {
summands: []string{"1", "2"},
},
"small numbers": {
summands: []string{"123", "0.2", "3.1415", "15"},
},
"medium numbers": {
summands: []string{"1234.567899", "9991345552.2340134"},
},
"big18": {
summands: []string{"123456789012345678", "123456789012345678", "123456789012345678", "123456789012345678", "123456789012345678", "123456789012345678"},
},
"growing numbers": {
summands: []string{"1", "100", "1000", "100000", "10000000", "10000000000", "10000000000000", "100000000000000000"},
},
"decimals": {
summands: []string{"0.1", "0.01", "0.001", "0.000001", "0.00000001", "0.00000000001", "0.00000000000001", "0.000000000000000001"},
},
}
for name, spec := range specs {
b.Run(name, func(b *testing.B) {
b.Run("LegacyDec", func(b *testing.B) {
summands := make([]LegacyDec, len(spec.summands))
for i, s := range spec.summands {
summands[i] = LegacyMustNewDecFromStr(s)
}
sum := LegacyNewDec(0)
b.ResetTimer()
for i := 0; i < b.N; i++ {
for _, s := range summands {
sum = sum.Add(s)
}
}
})
b.Run("NewDec", func(b *testing.B) {
summands := make([]Dec, len(spec.summands))
for i, s := range spec.summands {
summands[i] = must(NewDecFromString(s))
}
sum := NewDecFromInt64(0)
b.ResetTimer()
for i := 0; i < b.N; i++ {
for _, s := range summands {
sum, _ = sum.Add(s)
}
}
})
})
}
}
func BenchmarkCompareLegacyDecAndNewDecSub(b *testing.B) {
specs := map[string]struct {
minuend string
subtrahends []string
}{
"100 - 1 - 2": {
minuend: "100",
subtrahends: []string{"1", "2"},
},
"small numbers": {
minuend: "152.4013",
subtrahends: []string{"123", "0.2", "3.1415", "15"},
},
"10000000 - big18 numbers": {
minuend: "10000000",
subtrahends: []string{"123456789012345678", "123456789012345678", "123456789012345678", "123456789012345678", "123456789012345678", "123456789012345678"},
},
"10000000 - growing numbers": {
minuend: "10000000",
subtrahends: []string{"1", "100", "1000", "100000", "10000000", "10000000000", "10000000000000", "100000000000000000"},
},
"10000000 shrinking decimals": {
minuend: "10000000",
subtrahends: []string{"0.1", "0.01", "0.001", "0.000001", "0.00000001", "0.00000000001", "0.00000000000001", "0.000000000000000001"},
},
}
for name, spec := range specs {
b.Run(name, func(b *testing.B) {
b.Run("LegacyDec", func(b *testing.B) {
summands := make([]LegacyDec, len(spec.subtrahends))
for i, s := range spec.subtrahends {
summands[i] = LegacyMustNewDecFromStr(s)
}
diff := LegacyMustNewDecFromStr(spec.minuend)
b.ResetTimer()
for i := 0; i < b.N; i++ {
for _, s := range summands {
diff = diff.Sub(s)
}
}
})
b.Run("NewDec", func(b *testing.B) {
summands := make([]Dec, len(spec.subtrahends))
for i, s := range spec.subtrahends {
summands[i] = must(NewDecFromString(s))
}
diff := must(NewDecFromString(spec.minuend))
b.ResetTimer()
for i := 0; i < b.N; i++ {
for _, s := range summands {
diff, _ = diff.Sub(s)
}
}
})
})
}
}
func BenchmarkCompareLegacyDecAndNewDecMul(b *testing.B) {
specs := map[string]struct {
multiplier, multiplicant string
}{
"small/ small": {
multiplier: "100", multiplicant: "5",
},
"big18/ small": {
multiplier: "999999999999999999", multiplicant: "10",
},
"self18/ self18": {
multiplier: "999999999999999999", multiplicant: "999999999999999999",
},
"big18/ big18": {
multiplier: "888888888888888888", multiplicant: "444444444444444444",
},
"decimal18b/ decimal18c": {
multiplier: "8.88888888888888888", multiplicant: "4.1234567890123",
},
"small/ big18": {
multiplier: "100", multiplicant: "999999999999999999",
},
"big34/ big34": {
multiplier: "9999999999999999999999999999999999", multiplicant: "1999999999999999999999999999999999",
},
"negative big34": {
multiplier: "-9999999999999999999999999999999999", multiplicant: "999999999999999999999999999",
},
"decimal small": {
multiplier: "0.0000000001", multiplicant: "10",
},
"decimal small/decimal small ": {
multiplier: "0.0000000001", multiplicant: "0.0001",
},
}
for name, spec := range specs {
b.Run(name, func(b *testing.B) {
b.Run("LegacyDec", func(b *testing.B) {
dv, ds := LegacyMustNewDecFromStr(spec.multiplier), LegacyMustNewDecFromStr(spec.multiplicant)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = dv.Mul(ds)
}
})
b.Run("NewDec", func(b *testing.B) {
dv, ds := must(NewDecFromString(spec.multiplier)), must(NewDecFromString(spec.multiplicant))
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = dv.Mul(ds)
}
})
})
}
}
func BenchmarkCompareLegacyDecAndNewDecMarshalUnmarshal(b *testing.B) {
specs := map[string]struct {
src string
}{
"small": {
src: "1",
},
"big18": {
src: "999999999999999999",
},
"negative big34": {
src: "9999999999999999999999999999999999",
},
"decimal": {
src: "12345.678901234341",
},
}
for name, spec := range specs {
b.Run(name, func(b *testing.B) {
b.Run("LegacyDec", func(b *testing.B) {
src := LegacyMustNewDecFromStr(spec.src)
b.ResetTimer()
for i := 0; i < b.N; i++ {
bz, err := src.Marshal()
require.NoError(b, err)
var d LegacyDec
require.NoError(b, d.Unmarshal(bz))
}
})
b.Run("NewDec", func(b *testing.B) {
src := must(NewDecFromString(spec.src))
b.ResetTimer()
for i := 0; i < b.N; i++ {
bz, err := src.Marshal()
require.NoError(b, err)
var d Dec
require.NoError(b, d.Unmarshal(bz))
}
})
})
}
}
func BenchmarkCompareLegacyDecAndNewDecQuoInteger(b *testing.B) {
legacyB1 := LegacyNewDec(100)
newB1 := NewDecFromInt64(100)
b.Run("LegacyDec", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = legacyB1.Quo(LegacyNewDec(1))
}
})
b.Run("NewDec", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = newB1.QuoInteger(NewDecFromInt64(1))
}
})
}
func BenchmarkCompareLegacyAddAndDecAdd(b *testing.B) {
legacyB1 := LegacyNewDec(100)
legacyB2 := LegacyNewDec(5)
newB1 := NewDecFromInt64(100)
newB2 := NewDecFromInt64(5)
b.Run("LegacyDec", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = legacyB1.Add(legacyB2)
}
})
b.Run("NewDec", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = newB1.Add(newB2)
}
})
}
func BenchmarkCompareLegacySubAndDecMul(b *testing.B) {
legacyB1 := LegacyNewDec(100)
legacyB2 := LegacyNewDec(5)
newB1 := NewDecFromInt64(100)
newB2 := NewDecFromInt64(5)
b.Run("LegacyDec", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = legacyB1.Mul(legacyB2)
}
})
b.Run("NewDec", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = newB1.Mul(newB2)
}
})
}
func BenchmarkCompareLegacySubAndDecSub(b *testing.B) {
legacyB1 := LegacyNewDec(100)
legacyB2 := LegacyNewDec(5)
newB1 := NewDecFromInt64(100)
newB2 := NewDecFromInt64(5)
b.Run("LegacyDec", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = legacyB1.Sub(legacyB2)
}
})
b.Run("NewDec", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = newB1.Sub(newB2)
}
})
}

323
math/dec_examples_test.go Normal file
View File

@ -0,0 +1,323 @@
package math
import "fmt"
func ExampleDec() {
d := NewDecFromInt64(1) // 1
fmt.Println(d.String())
d = NewDecWithExp(-1234, -3) // -1.234
fmt.Println(d.String())
d = NewDecWithExp(1234, 0) // 1234
fmt.Println(d.String())
d = NewDecWithExp(1234, 1) // 12340
fmt.Println(d.String())
// scientific notation
d, err := NewDecFromString("1.23E+4") // 12300
if err != nil {
panic(err)
}
fmt.Println(d.String())
// decimal notation
d, err = NewDecFromString("1.234")
if err != nil {
panic(err)
}
fmt.Println(d.String())
// Output: 1
// -1.234
// 1234
// 12340
// 12300
// 1.234
}
func ExampleDec_Add() {
sum, err := NewDecFromInt64(1).Add(NewDecFromInt64(1)) // 1 + 1 = 2
if err != nil {
panic(err)
}
fmt.Println(sum.String())
const maxExp = 100_000
_, err = NewDecWithExp(1, maxExp).Add(NewDecFromInt64(1)) // 1E+1000000 + 1
if err != nil {
fmt.Println(err.Error())
}
sum, err = NewDecWithExp(1, maxExp).Add(NewDecWithExp(1, maxExp)) // 1E+1000000 + 1E+1000000
if err != nil {
panic(err)
}
fmt.Println(sum.Text('E'))
// the max exponent must not be exceeded
_, err = NewDecWithExp(1, maxExp+1).Add(NewDecFromInt64(1)) // 1E+1000001 + 1
if err != nil {
fmt.Println(err.Error())
}
const minExp = -100_000
// same for min exponent
_, err = NewDecWithExp(1, minExp-1).Add(NewDecFromInt64(1)) // 1E-1000001 + 1
if err != nil {
fmt.Println(err.Error())
}
// not even by adding 0
_, err = NewDecWithExp(1, minExp-1).Add(NewDecFromInt64(0)) // 1E-1000001 + 0
if err != nil {
fmt.Println(err.Error())
}
// Output: 2
// 2E+100000
// add: exponent out of range: invalid decimal
// add: exponent out of range: invalid decimal
// add: exponent out of range: invalid decimal
}
func ExampleDec_Sub() {
sum, err := NewDecFromInt64(2).Sub(NewDecFromInt64(1)) // 2 - 1
if err != nil {
panic(err)
}
fmt.Println(sum.String())
const maxExp = 100_000
_, err = NewDecWithExp(1, maxExp).Sub(NewDecFromInt64(1)) // 1E+1000000 - 1
if err != nil {
fmt.Println(err.Error())
}
sum, err = NewDecWithExp(1, maxExp).Sub(NewDecWithExp(1, maxExp)) // 1E+1000000 - 1E+1000000
if err != nil {
panic(err)
}
fmt.Println(sum.Text('E'))
// the max exponent must not be exceeded
_, err = NewDecWithExp(1, maxExp+1).Sub(NewDecFromInt64(1)) // 1E+1000001 - 1
if err != nil {
fmt.Println(err.Error())
}
const minExp = -100_000
// same for min exponent
_, err = NewDecWithExp(1, minExp-1).Sub(NewDecFromInt64(1)) // 1E-1000001 - 1
if err != nil {
fmt.Println(err.Error())
}
// not even by adding 0
_, err = NewDecWithExp(1, minExp-1).Sub(NewDecFromInt64(0)) // 1E-1000001 - 0
if err != nil {
fmt.Println(err.Error())
}
// Output: 1
// 0E+100000
// sub: exponent out of range: invalid decimal
// sub: exponent out of range: invalid decimal
// sub: exponent out of range: invalid decimal
}
func ExampleDec_Quo() {
sum, err := NewDecFromInt64(6).Quo(NewDecFromInt64(2)) // 6 / 2
if err != nil {
panic(err)
}
fmt.Println(sum.String())
sum, err = NewDecFromInt64(7).Quo(NewDecFromInt64(2)) // 7 / 2
if err != nil {
panic(err)
}
fmt.Println(sum.String())
sum, err = NewDecFromInt64(4).Quo(NewDecFromInt64(9)) // 4 / 9
if err != nil {
panic(err)
}
fmt.Println(sum.String())
const minExp = -100_000
sum, err = NewDecWithExp(1, minExp).Quo(NewDecFromInt64(10)) // 1e-100000 / 10
if err != nil {
fmt.Println(err.Error())
}
sum, err = NewDecFromInt64(1).Quo(NewDecFromInt64(0)) // 1 / 0 -> error
if err != nil {
fmt.Println(err.Error())
}
// Output: 3.000000000000000000000000000000000
// 3.500000000000000000000000000000000
// 0.4444444444444444444444444444444444
// exponent out of range: invalid decimal
// division by zero: invalid decimal
}
func ExampleDec_QuoExact() {
sum, err := NewDecFromInt64(6).QuoExact(NewDecFromInt64(2)) // 6 / 2
if err != nil {
panic(err)
}
fmt.Println(sum.String())
sum, err = NewDecFromInt64(7).QuoExact(NewDecFromInt64(2)) // 7 / 2
if err != nil {
panic(err)
}
fmt.Println(sum.String())
sum, err = NewDecFromInt64(4).QuoExact(NewDecFromInt64(9)) // 4 / 9 -> error
if err != nil {
fmt.Println(err.Error())
}
const minExp = -100_000
sum, err = NewDecWithExp(1, minExp).QuoExact(NewDecFromInt64(10)) // 1e-100000 / 10 -> error
if err != nil {
fmt.Println(err.Error())
}
sum, err = NewDecFromInt64(1).QuoExact(NewDecFromInt64(0)) // 1 / 0 -> error
if err != nil {
fmt.Println(err.Error())
}
// Output: 3.000000000000000000000000000000000
// 3.500000000000000000000000000000000
// unexpected rounding
// exponent out of range: invalid decimal
// division by zero: invalid decimal
}
func ExampleDec_QuoInteger() {
sum, err := NewDecFromInt64(6).QuoInteger(NewDecFromInt64(2)) // 6 / 2
if err != nil {
panic(err)
}
fmt.Println(sum.String())
sum, err = NewDecFromInt64(7).QuoInteger(NewDecFromInt64(2)) // 7 / 2
if err != nil {
panic(err)
}
fmt.Println(sum.String())
sum, err = NewDecFromInt64(4).QuoInteger(NewDecFromInt64(9)) // 4 / 9 -> error
if err != nil {
panic(err)
}
fmt.Println(sum.String())
const minExp = -100_000
sum, err = NewDecWithExp(1, minExp).QuoInteger(NewDecFromInt64(10)) // 1e-100000 / 10 -> 0
if err != nil {
panic(err)
}
fmt.Println(sum.String())
sum, err = NewDecFromInt64(1).QuoInteger(NewDecFromInt64(0)) // 1 / 0 -> error
if err != nil {
fmt.Println(err.Error())
}
// Output: 3
// 3
// 0
// 0
// division by zero: invalid decimal
}
func ExampleDec_Mul() {
sum, err := NewDecFromInt64(2).Mul(NewDecFromInt64(3)) // 2 * 3
if err != nil {
panic(err)
}
fmt.Println(sum.String())
sum, err = NewDecWithExp(125, -2).Mul(NewDecFromInt64(2)) // 1.25 * 2
if err != nil {
panic(err)
}
fmt.Println(sum.String())
const maxExp = 100_000
sum, err = NewDecWithExp(1, maxExp).Mul(NewDecFromInt64(10)) // 1e100000 * 10 -> err
if err != nil {
fmt.Println(err.Error())
}
sum, err = NewDecFromInt64(1).Mul(NewDecFromInt64(0)) // 1 * 0
if err != nil {
panic(err)
}
fmt.Println(sum.String())
// Output: 6
// 2.50
// exponent out of range: invalid decimal
// 0
}
func ExampleDec_MulExact() {
sum, err := NewDecFromInt64(2).MulExact(NewDecFromInt64(3)) // 2 * 3
if err != nil {
panic(err)
}
fmt.Println(sum.String())
sum, err = NewDecWithExp(125, -2).MulExact(NewDecFromInt64(2)) // 1.25 * 2
if err != nil {
panic(err)
}
fmt.Println(sum.String())
const maxExp = 100_000
sum, err = NewDecWithExp(1, maxExp).MulExact(NewDecFromInt64(10)) // 1e100000 * 10 -> err
if err != nil {
fmt.Println(err.Error())
}
a, err := NewDecFromString("0.12345678901234567890123456789012345") // 35 digits after the comma
if err != nil {
panic(err)
}
sum, err = a.MulExact(NewDecFromInt64(1))
if err != nil {
fmt.Println(err.Error())
}
sum, err = a.MulExact(NewDecFromInt64(0))
if err != nil {
panic(err)
}
fmt.Println(sum.String())
sum, err = NewDecFromInt64(1).MulExact(NewDecFromInt64(0)) // 1 * 0
if err != nil {
panic(err)
}
fmt.Println(sum.String())
// Output: 6
// 2.50
// exponent out of range: invalid decimal
// unexpected rounding
// 0E-35
// 0
}
func ExampleDec_Modulo() {
sum, err := NewDecFromInt64(7).Modulo(NewDecFromInt64(3)) // 7 mod 3 = 1
if err != nil {
panic(err)
}
fmt.Println(sum.String())
// Output: 1
}

527
math/dec_rapid_test.go Normal file
View File

@ -0,0 +1,527 @@
package math
import (
"fmt"
"regexp"
"strconv"
"testing"
"github.com/stretchr/testify/require"
"pgregory.net/rapid"
)
// Rapid is a Go library for property-based testing.
func TestDecWithRapid(t *testing.T) {
// Property tests
t.Run("TestNewDecFromInt64", rapid.MakeCheck(testDecInt64))
// Properties about *FromString functions
t.Run("TestInvalidNewDecFromString", rapid.MakeCheck(testInvalidNewDecFromString))
// Properties about addition
t.Run("TestAddLeftIdentity", rapid.MakeCheck(testAddLeftIdentity))
t.Run("TestAddRightIdentity", rapid.MakeCheck(testAddRightIdentity))
t.Run("TestAddCommutative", rapid.MakeCheck(testAddCommutative))
t.Run("TestAddAssociative", rapid.MakeCheck(testAddAssociative))
// Properties about subtraction
t.Run("TestSubRightIdentity", rapid.MakeCheck(testSubRightIdentity))
t.Run("TestSubZero", rapid.MakeCheck(testSubZero))
// Properties about multiplication
t.Run("TestMulLeftIdentity", rapid.MakeCheck(testMulLeftIdentity))
t.Run("TestMulRightIdentity", rapid.MakeCheck(testMulRightIdentity))
t.Run("TestMulCommutative", rapid.MakeCheck(testMulCommutative))
t.Run("TestMulAssociative", rapid.MakeCheck(testMulAssociative))
t.Run("TestZeroIdentity", rapid.MakeCheck(testMulZero))
// Properties about division
t.Run("TestDivisionBySelf", rapid.MakeCheck(testSelfQuo))
t.Run("TestDivisionByOne", rapid.MakeCheck(testQuoByOne))
// Properties combining operations
t.Run("TestSubAdd", rapid.MakeCheck(testSubAdd))
t.Run("TestAddSub", rapid.MakeCheck(testAddSub))
t.Run("TestMulQuoA", rapid.MakeCheck(testMulQuoA))
t.Run("TestMulQuoB", rapid.MakeCheck(testMulQuoB))
t.Run("TestMulQuoExact", rapid.MakeCheck(testMulQuoExact))
t.Run("TestQuoMulExact", rapid.MakeCheck(testQuoMulExact))
// Properties about comparison and equality
t.Run("TestCmpInverse", rapid.MakeCheck(testCmpInverse))
t.Run("TestEqualCommutative", rapid.MakeCheck(testEqualCommutative))
// Properties about tests on a single Dec
t.Run("TestIsZero", rapid.MakeCheck(testIsZero))
t.Run("TestIsNegative", rapid.MakeCheck(testIsNegative))
t.Run("TestIsPositive", rapid.MakeCheck(testIsPositive))
t.Run("TestNumDecimalPlaces", rapid.MakeCheck(testNumDecimalPlaces))
// Unit tests
zero := Dec{}
one := NewDecFromInt64(1)
two := NewDecFromInt64(2)
three := NewDecFromInt64(3)
four := NewDecFromInt64(4)
five := NewDecFromInt64(5)
minusOne := NewDecFromInt64(-1)
onePointOneFive, err := NewDecFromString("1.15")
require.NoError(t, err)
twoPointThreeFour, err := NewDecFromString("2.34")
require.NoError(t, err)
threePointFourNine, err := NewDecFromString("3.49")
require.NoError(t, err)
onePointFourNine, err := NewDecFromString("1.49")
require.NoError(t, err)
minusFivePointZero, err := NewDecFromString("-5.0")
require.NoError(t, err)
twoThousand := NewDecWithExp(2, 3)
require.True(t, twoThousand.Equal(NewDecFromInt64(2000)))
res, err := two.Add(zero)
require.NoError(t, err)
require.True(t, res.Equal(two))
res, err = five.Sub(two)
require.NoError(t, err)
require.True(t, res.Equal(three))
res, err = four.Quo(two)
require.NoError(t, err)
require.True(t, res.Equal(two))
res, err = five.QuoInteger(two)
require.NoError(t, err)
require.True(t, res.Equal(two))
res, err = five.Modulo(two)
require.NoError(t, err)
require.True(t, res.Equal(one))
x, err := four.Int64()
require.NoError(t, err)
require.Equal(t, int64(4), x)
require.Equal(t, "5", five.String())
res, err = onePointOneFive.Add(twoPointThreeFour)
require.NoError(t, err)
require.True(t, res.Equal(threePointFourNine))
res, err = threePointFourNine.Sub(two)
require.NoError(t, err)
require.True(t, res.Equal(onePointFourNine))
res, err = minusOne.Sub(four)
require.NoError(t, err)
require.True(t, res.Equal(minusFivePointZero))
require.True(t, zero.IsZero())
require.False(t, zero.IsPositive())
require.False(t, zero.IsNegative())
require.False(t, one.IsZero())
require.True(t, one.IsPositive())
require.False(t, one.IsNegative())
require.False(t, minusOne.IsZero())
require.False(t, minusOne.IsPositive())
require.True(t, minusOne.IsNegative())
res, err = one.MulExact(two)
require.NoError(t, err)
require.True(t, res.Equal(two))
}
var genDec *rapid.Generator[Dec] = rapid.Custom(func(t *rapid.T) Dec {
f := rapid.Float64().Draw(t, "f")
dec, err := NewDecFromString(fmt.Sprintf("%g", f))
require.NoError(t, err)
return dec
})
// A Dec value and the float used to create it
type floatAndDec struct {
float float64
dec Dec
}
// Generate a Dec value along with the float used to create it
var genFloatAndDec *rapid.Generator[floatAndDec] = rapid.Custom(func(t *rapid.T) floatAndDec {
f := rapid.Float64().Draw(t, "f")
dec, err := NewDecFromString(fmt.Sprintf("%g", f))
require.NoError(t, err)
return floatAndDec{f, dec}
})
// Property: n == NewDecFromInt64(n).Int64()
func testDecInt64(t *rapid.T) {
nIn := rapid.Int64().Draw(t, "n")
nOut, err := NewDecFromInt64(nIn).Int64()
require.NoError(t, err)
require.Equal(t, nIn, nOut)
}
// Property: invalid_number_string(s) => NewDecFromString(s) == err
func testInvalidNewDecFromString(t *rapid.T) {
s := rapid.StringMatching("[[:alpha:]]+").Draw(t, "s")
_, err := NewDecFromString(s)
require.Error(t, err)
}
// Property: 0 + a == a
func testAddLeftIdentity(t *rapid.T) {
a := genDec.Draw(t, "a")
zero := NewDecFromInt64(0)
b, err := zero.Add(a)
require.NoError(t, err)
require.True(t, a.Equal(b))
}
// Property: a + 0 == a
func testAddRightIdentity(t *rapid.T) {
a := genDec.Draw(t, "a")
zero := NewDecFromInt64(0)
b, err := a.Add(zero)
require.NoError(t, err)
require.True(t, a.Equal(b))
}
// Property: a + b == b + a
func testAddCommutative(t *rapid.T) {
a := genDec.Draw(t, "a")
b := genDec.Draw(t, "b")
c, err := a.Add(b)
require.NoError(t, err)
d, err := b.Add(a)
require.NoError(t, err)
require.True(t, c.Equal(d))
}
// Property: (a + b) + c == a + (b + c)
func testAddAssociative(t *rapid.T) {
a := genDec.Draw(t, "a")
b := genDec.Draw(t, "b")
c := genDec.Draw(t, "c")
// (a + b) + c
d, err := a.Add(b)
require.NoError(t, err)
e, err := d.Add(c)
require.NoError(t, err)
// a + (b + c)
f, err := b.Add(c)
require.NoError(t, err)
g, err := a.Add(f)
require.NoError(t, err)
require.True(t, e.Equal(g))
}
// Property: a - 0 == a
func testSubRightIdentity(t *rapid.T) {
a := genDec.Draw(t, "a")
zero := NewDecFromInt64(0)
b, err := a.Sub(zero)
require.NoError(t, err)
require.True(t, a.Equal(b))
}
// Property: a - a == 0
func testSubZero(t *rapid.T) {
a := genDec.Draw(t, "a")
zero := NewDecFromInt64(0)
b, err := a.Sub(a)
require.NoError(t, err)
require.True(t, b.Equal(zero))
}
// Property: 1 * a == a
func testMulLeftIdentity(t *rapid.T) {
a := genDec.Draw(t, "a")
one := NewDecFromInt64(1)
b, err := one.Mul(a)
require.NoError(t, err)
require.True(t, a.Equal(b))
}
// Property: a * 1 == a
func testMulRightIdentity(t *rapid.T) {
a := genDec.Draw(t, "a")
one := NewDecFromInt64(1)
b, err := a.Mul(one)
require.NoError(t, err)
require.True(t, a.Equal(b))
}
// Property: a * b == b * a
func testMulCommutative(t *rapid.T) {
a := genDec.Draw(t, "a")
b := genDec.Draw(t, "b")
c, err := a.Mul(b)
require.NoError(t, err)
d, err := b.Mul(a)
require.NoError(t, err)
require.True(t, c.Equal(d))
}
// Property: (a * b) * c == a * (b * c)
func testMulAssociative(t *rapid.T) {
a := genDec.Draw(t, "a")
b := genDec.Draw(t, "b")
c := genDec.Draw(t, "c")
// (a * b) * c
d, err := a.Mul(b)
require.NoError(t, err)
e, err := d.Mul(c)
require.NoError(t, err)
// a * (b * c)
f, err := b.Mul(c)
require.NoError(t, err)
g, err := a.Mul(f)
require.NoError(t, err)
require.True(t, e.Equal(g))
}
// Property: (a - b) + b == a
func testSubAdd(t *rapid.T) {
a := genDec.Draw(t, "a")
b := genDec.Draw(t, "b")
c, err := a.Sub(b)
require.NoError(t, err)
d, err := c.Add(b)
require.NoError(t, err)
require.True(t, a.Equal(d))
}
// Property: (a + b) - b == a
func testAddSub(t *rapid.T) {
a := genDec.Draw(t, "a")
b := genDec.Draw(t, "b")
c, err := a.Add(b)
require.NoError(t, err)
d, err := c.Sub(b)
require.NoError(t, err)
require.True(t, a.Equal(d))
}
// Property: a * 0 = 0
func testMulZero(t *rapid.T) {
a := genDec.Draw(t, "a")
zero := Dec{}
c, err := a.Mul(zero)
require.NoError(t, err)
require.True(t, c.IsZero())
}
// Property: a/a = 1
func testSelfQuo(t *rapid.T) {
decNotZero := func(d Dec) bool { return !d.IsZero() }
a := genDec.Filter(decNotZero).Draw(t, "a")
one := NewDecFromInt64(1)
b, err := a.Quo(a)
require.NoError(t, err)
require.True(t, one.Equal(b))
}
// Property: a/1 = a
func testQuoByOne(t *rapid.T) {
a := genDec.Draw(t, "a")
one := NewDecFromInt64(1)
b, err := a.Quo(one)
require.NoError(t, err)
require.True(t, a.Equal(b))
}
// Property: (a * b) / a == b
func testMulQuoA(t *rapid.T) {
decNotZero := func(d Dec) bool { return !d.IsZero() }
a := genDec.Filter(decNotZero).Draw(t, "a")
b := genDec.Draw(t, "b")
c, err := a.Mul(b)
require.NoError(t, err)
d, err := c.Quo(a)
require.NoError(t, err)
require.True(t, b.Equal(d))
}
// Property: (a * b) / b == a
func testMulQuoB(t *rapid.T) {
decNotZero := func(d Dec) bool { return !d.IsZero() }
a := genDec.Draw(t, "a")
b := genDec.Filter(decNotZero).Draw(t, "b")
c, err := a.Mul(b)
require.NoError(t, err)
d, err := c.Quo(b)
require.NoError(t, err)
require.True(t, a.Equal(d))
}
// Property: (a * 10^b) / 10^b == a using MulExact and QuoExact
// and a with no more than b decimal places (b <= 32).
func testMulQuoExact(t *rapid.T) {
b := rapid.Uint32Range(0, 32).Draw(t, "b")
decPrec := func(d Dec) bool { return d.NumDecimalPlaces() <= b }
a := genDec.Filter(decPrec).Draw(t, "a")
c := NewDecWithExp(1, int32(b))
d, err := a.MulExact(c)
require.NoError(t, err)
e, err := d.QuoExact(c)
require.NoError(t, err)
require.True(t, a.Equal(e))
}
// Property: (a / b) * b == a using QuoExact and MulExact and
// a as an integer.
func testQuoMulExact(t *rapid.T) {
a := rapid.Uint64().Draw(t, "a")
aDec, err := NewDecFromString(fmt.Sprintf("%d", a))
require.NoError(t, err)
b := rapid.Uint32Range(0, 32).Draw(t, "b")
c := NewDecWithExp(1, int32(b))
require.NoError(t, err)
d, err := aDec.QuoExact(c)
require.NoError(t, err)
e, err := d.MulExact(c)
require.NoError(t, err)
require.True(t, aDec.Equal(e))
}
// Property: Cmp(a, b) == -Cmp(b, a)
func testCmpInverse(t *rapid.T) {
a := genDec.Draw(t, "a")
b := genDec.Draw(t, "b")
require.Equal(t, a.Cmp(b), -b.Cmp(a))
}
// Property: Equal(a, b) == Equal(b, a)
func testEqualCommutative(t *rapid.T) {
a := genDec.Draw(t, "a")
b := genDec.Draw(t, "b")
require.Equal(t, a.Equal(b), b.Equal(a))
}
// Property: isZero(f) == isZero(NewDecFromString(f.String()))
func testIsZero(t *rapid.T) {
floatAndDec := genFloatAndDec.Draw(t, "floatAndDec")
f, dec := floatAndDec.float, floatAndDec.dec
require.Equal(t, f == 0, dec.IsZero())
}
// Property: isNegative(f) == isNegative(NewDecFromString(f.String()))
func testIsNegative(t *rapid.T) {
floatAndDec := genFloatAndDec.Draw(t, "floatAndDec")
f, dec := floatAndDec.float, floatAndDec.dec
require.Equal(t, f < 0, dec.IsNegative())
}
// Property: isPositive(f) == isPositive(NewDecFromString(f.String()))
func testIsPositive(t *rapid.T) {
floatAndDec := genFloatAndDec.Draw(t, "floatAndDec")
f, dec := floatAndDec.float, floatAndDec.dec
require.Equal(t, f > 0, dec.IsPositive())
}
// Property: floatDecimalPlaces(f) == NumDecimalPlaces(NewDecFromString(f.String()))
func testNumDecimalPlaces(t *rapid.T) {
floatAndDec := genFloatAndDec.Draw(t, "floatAndDec")
f, dec := floatAndDec.float, floatAndDec.dec
require.Equal(t, floatDecimalPlaces(t, f), dec.NumDecimalPlaces())
}
func floatDecimalPlaces(t *rapid.T, f float64) uint32 {
reScientific := regexp.MustCompile(`^\-?(?:[[:digit:]]+(?:\.([[:digit:]]+))?|\.([[:digit:]]+))(?:e?(?:\+?([[:digit:]]+)|(-[[:digit:]]+)))?$`)
fStr := fmt.Sprintf("%g", f)
matches := reScientific.FindAllStringSubmatch(fStr, 1)
if len(matches) != 1 {
t.Fatalf("Didn't match float: %g", f)
}
// basePlaces is the number of decimal places in the decimal part of the
// string
basePlaces := 0
if matches[0][1] != "" {
basePlaces = len(matches[0][1])
} else if matches[0][2] != "" {
basePlaces = len(matches[0][2])
}
t.Logf("Base places: %d", basePlaces)
// exp is the exponent
exp := 0
if matches[0][3] != "" {
var err error
exp, err = strconv.Atoi(matches[0][3])
require.NoError(t, err)
} else if matches[0][4] != "" {
var err error
exp, err = strconv.Atoi(matches[0][4])
require.NoError(t, err)
}
// Subtract exponent from base and check if negative
res := basePlaces - exp
if res <= 0 {
return 0
}
return uint32(res)
}

File diff suppressed because it is too large Load Diff

View File

@ -1,17 +1,39 @@
module cosmossdk.io/math
go 1.23
go 1.22
require (
github.com/stretchr/testify v1.8.4
sigs.k8s.io/yaml v1.3.0
github.com/cockroachdb/apd/v3 v3.2.1
github.com/stretchr/testify v1.10.0
sigs.k8s.io/yaml v1.4.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
golang.org/x/net v0.34.0 // indirect
golang.org/x/sys v0.29.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250122153221-138b5a5a4fd4 // indirect
google.golang.org/grpc v1.70.0 // indirect
google.golang.org/protobuf v1.36.4 // indirect
)
require (
cosmossdk.io/errors v1.0.1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
pgregory.net/rapid v1.1.0
)
// Issue with math.Int{}.Size() implementation.
retract [v1.1.0, v1.1.1]
// Bit length differences between Int and Dec
retract (
v1.3.0
v1.2.0
v1.1.2
[v1.0.0, v1.0.1]
)

View File

@ -1,26 +1,49 @@
cosmossdk.io/errors v1.0.1 h1:bzu+Kcr0kS/1DuPBtUFdWjzLqyUuCiyHjyJB6srBV/0=
cosmossdk.io/errors v1.0.1/go.mod h1:MeelVSZThMi4bEakzhhhE/CKqVv3nOJDA25bIqRDu/U=
github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg=
github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250122153221-138b5a5a4fd4 h1:yrTuav+chrF0zF/joFGICKTzYv7mh/gr9AgEXrVU8ao=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250122153221-138b5a5a4fd4/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
pgregory.net/rapid v1.1.0 h1:CMa0sjHSru3puNx+J0MIAuiiEV4N0qj8/cMWGBBCsjw=
pgregory.net/rapid v1.1.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=

View File

@ -3,8 +3,10 @@ package math
import (
"encoding"
"encoding/json"
"errors"
"fmt"
"math/big"
"math/bits"
"strings"
"sync"
"testing"
@ -13,6 +15,20 @@ import (
// MaxBitLen defines the maximum bit length supported bit Int and Uint types.
const MaxBitLen = 256
// maxWordLen defines the maximum word length supported by Int and Uint types.
// We check overflow, by first doing a fast check if the word length is below maxWordLen
// and if not then do the slower full bitlen check.
// NOTE: If MaxBitLen is not a multiple of bits.UintSize, then we need to edit the used logic slightly.
const maxWordLen = MaxBitLen / bits.UintSize
// Integer errors
var (
// ErrIntOverflow is the error returned when an integer overflow occurs
ErrIntOverflow = errors.New("integer overflow")
// ErrDivideByZero is the error returned when a divide by zero occurs
ErrDivideByZero = errors.New("divide by zero")
)
func newIntegerFromString(s string) (*big.Int, bool) {
return new(big.Int).SetString(s, 0)
}
@ -62,7 +78,7 @@ func unmarshalText(i *big.Int, text string) error {
return err
}
if i.BitLen() > MaxBitLen {
if bigIntOverflows(i) {
return fmt.Errorf("integer out of range: %s", text)
}
@ -71,7 +87,7 @@ func unmarshalText(i *big.Int, text string) error {
var _ customProtobufType = (*Int)(nil)
// Int wraps big.Int with a 257 bit range bound
// Int wraps big.Int with a 256 bit range bound
// Checks overflow, underflow and division by zero
// Exists in range from -(2^256 - 1) to 2^256 - 1
type Int struct {
@ -86,6 +102,14 @@ func (i Int) BigInt() *big.Int {
return new(big.Int).Set(i.i)
}
// BigIntMut converts Int to big.Int, mutative the input
func (i Int) BigIntMut() *big.Int {
if i.IsNil() {
return nil
}
return i.i
}
// IsNil returns true if Int is uninitialized
func (i Int) IsNil() bool {
return i.i == nil
@ -105,14 +129,31 @@ func NewIntFromUint64(n uint64) Int {
// NewIntFromBigInt constructs Int from big.Int. If the provided big.Int is nil,
// it returns an empty instance. This function panics if the bit length is > 256.
// Note, the caller can safely mutate the argument after this function returns.
func NewIntFromBigInt(i *big.Int) Int {
if i == nil {
return Int{}
}
if i.BitLen() > MaxBitLen {
if bigIntOverflows(i) {
panic("NewIntFromBigInt() out of bound")
}
return Int{new(big.Int).Set(i)}
}
// NewIntFromBigIntMut constructs Int from big.Int. If the provided big.Int is nil,
// it returns an empty instance. This function panics if the bit length is > 256.
// Note, this function mutate the argument.
func NewIntFromBigIntMut(i *big.Int) Int {
if i == nil {
return Int{}
}
if bigIntOverflows(i) {
panic("NewIntFromBigInt() out of bound")
}
return Int{i}
}
@ -123,7 +164,7 @@ func NewIntFromString(s string) (res Int, ok bool) {
return
}
// Check overflow
if i.BitLen() > MaxBitLen {
if bigIntOverflows(i) {
ok = false
return
}
@ -141,7 +182,7 @@ func NewIntWithDecimal(n int64, dec int) Int {
i.Mul(big.NewInt(n), exp)
// Check overflow
if i.BitLen() > MaxBitLen {
if bigIntOverflows(i) {
panic("NewIntWithDecimal() out of bound")
}
return Int{i}
@ -153,6 +194,11 @@ func ZeroInt() Int { return Int{big.NewInt(0)} }
// OneInt returns Int value with one
func OneInt() Int { return Int{big.NewInt(1)} }
// ToLegacyDec converts Int to LegacyDec
func (i Int) ToLegacyDec() LegacyDec {
return LegacyNewDecFromInt(i)
}
// Int64 converts Int to int64
// Panics if the value is out of range
func (i Int) Int64() int64 {
@ -229,12 +275,12 @@ func (i Int) LTE(i2 Int) bool {
// Add adds Int from another
func (i Int) Add(i2 Int) (res Int) {
res = Int{add(i.i, i2.i)}
// Check overflow
if res.i.BitLen() > MaxBitLen {
panic("Int overflow")
x, err := i.SafeAdd(i2)
if err != nil {
panic(err)
}
return
return x
}
// AddRaw adds int64 to Int
@ -242,14 +288,24 @@ func (i Int) AddRaw(i2 int64) Int {
return i.Add(NewInt(i2))
}
// SafeAdd adds Int from another and returns an error if overflow
func (i Int) SafeAdd(i2 Int) (res Int, err error) {
res = Int{add(i.i, i2.i)}
// Check overflow
if bigIntOverflows(res.i) {
return Int{}, ErrIntOverflow
}
return res, nil
}
// Sub subtracts Int from another
func (i Int) Sub(i2 Int) (res Int) {
res = Int{sub(i.i, i2.i)}
// Check overflow
if res.i.BitLen() > MaxBitLen {
panic("Int overflow")
x, err := i.SafeSub(i2)
if err != nil {
panic(err)
}
return
return x
}
// SubRaw subtracts int64 from Int
@ -257,32 +313,49 @@ func (i Int) SubRaw(i2 int64) Int {
return i.Sub(NewInt(i2))
}
// SafeSub subtracts Int from another and returns an error if overflow or underflow
func (i Int) SafeSub(i2 Int) (res Int, err error) {
res = Int{sub(i.i, i2.i)}
// Check overflow/underflow
if bigIntOverflows(res.i) {
return Int{}, ErrIntOverflow
}
return res, nil
}
// Mul multiples two Ints
func (i Int) Mul(i2 Int) (res Int) {
// Check overflow
if i.i.BitLen()+i2.i.BitLen()-1 > MaxBitLen {
panic("Int overflow")
x, err := i.SafeMul(i2)
if err != nil {
panic(err)
}
res = Int{mul(i.i, i2.i)}
// Check overflow if sign of both are same
if res.i.BitLen() > MaxBitLen {
panic("Int overflow")
}
return
return x
}
// MulRaw multipies Int and int64
// MulRaw multiplies Int and int64
func (i Int) MulRaw(i2 int64) Int {
return i.Mul(NewInt(i2))
}
// SafeMul multiples Int from another and returns an error if overflow
func (i Int) SafeMul(i2 Int) (res Int, err error) {
res = Int{mul(i.i, i2.i)}
// Check overflow
if bigIntOverflows(res.i) {
return Int{}, ErrIntOverflow
}
return res, nil
}
// Quo divides Int with Int
func (i Int) Quo(i2 Int) (res Int) {
// Check division-by-zero
if i2.i.Sign() == 0 {
x, err := i.SafeQuo(i2)
if err != nil {
panic("Division by zero")
}
return Int{div(i.i, i2.i)}
return x
}
// QuoRaw divides Int with int64
@ -290,12 +363,22 @@ func (i Int) QuoRaw(i2 int64) Int {
return i.Quo(NewInt(i2))
}
// SafeQuo divides Int with Int and returns an error if division by zero
func (i Int) SafeQuo(i2 Int) (res Int, err error) {
// Check division-by-zero
if i2.i.Sign() == 0 {
return Int{}, ErrDivideByZero
}
return Int{div(i.i, i2.i)}, nil
}
// Mod returns remainder after dividing with Int
func (i Int) Mod(i2 Int) Int {
if i2.Sign() == 0 {
panic("division-by-zero")
x, err := i.SafeMod(i2)
if err != nil {
panic(err)
}
return Int{mod(i.i, i2.i)}
return x
}
// ModRaw returns remainder after dividing with int64
@ -303,6 +386,14 @@ func (i Int) ModRaw(i2 int64) Int {
return i.Mod(NewInt(i2))
}
// SafeMod returns remainder after dividing with Int and returns an error if division by zero
func (i Int) SafeMod(i2 Int) (res Int, err error) {
if i2.Sign() == 0 {
return Int{}, ErrDivideByZero
}
return Int{mod(i.i, i2.i)}, nil
}
// Neg negates Int
func (i Int) Neg() (res Int) {
return Int{neg(i.i)}
@ -313,7 +404,7 @@ func (i Int) Abs() Int {
return Int{abs(i.i)}
}
// return the minimum of the ints
// MinInt return the minimum of the ints
func MinInt(i1, i2 Int) Int {
return Int{min(i1.BigInt(), i2.BigInt())}
}
@ -323,7 +414,7 @@ func MaxInt(i, i2 Int) Int {
return Int{max(i.BigInt(), i2.BigInt())}
}
// Human readable string
// String returns human-readable string
func (i Int) String() string {
return i.i.String()
}
@ -344,7 +435,7 @@ func (i *Int) UnmarshalJSON(bz []byte) error {
return unmarshalJSON(i.i, bz)
}
// MarshalJSON for custom encoding scheme
// marshalJSON for custom encoding scheme
// Must be encoded as a string for JSON precision
func marshalJSON(i encoding.TextMarshaler) ([]byte, error) {
text, err := i.MarshalText()
@ -355,7 +446,7 @@ func marshalJSON(i encoding.TextMarshaler) ([]byte, error) {
return json.Marshal(string(text))
}
// UnmarshalJSON for custom decoding scheme
// unmarshalJSON for custom decoding scheme
// Must be encoded as a string for JSON precision
func unmarshalJSON(i *big.Int, bz []byte) error {
var text string
@ -413,7 +504,7 @@ func (i *Int) Unmarshal(data []byte) error {
return err
}
if i.i.BitLen() > MaxBitLen {
if bigIntOverflows(i.i) {
return fmt.Errorf("integer out of range; got: %d, max: %d", i.i.BitLen(), MaxBitLen)
}
@ -426,12 +517,13 @@ func (i *Int) Size() int {
return len(bz)
}
// Override Amino binary serialization by proxying to protobuf.
// MarshalAmino Override Amino binary serialization by proxying to protobuf.
func (i Int) MarshalAmino() ([]byte, error) { return i.Marshal() }
func (i *Int) UnmarshalAmino(bz []byte) error { return i.Unmarshal(bz) }
// intended to be used with require/assert: require.True(IntEq(...))
// IntEq intended to be used with require/assert: require.True(IntEq(...))
func IntEq(t *testing.T, exp, got Int) (*testing.T, bool, string, string, string) {
t.Helper()
return t, exp.Equal(got), "expected:\t%v\ngot:\t\t%v", exp.String(), got.String()
}
@ -458,7 +550,7 @@ var stringsBuilderPool = &sync.Pool{
// (instead of manipulating the int or math.Int object).
func FormatInt(v string) (string, error) {
if len(v) == 0 {
return "", fmt.Errorf("cannot format empty string")
return "", errors.New("cannot format empty string")
}
sign := ""
@ -506,3 +598,15 @@ func FormatInt(v string) (string, error) {
return sign + sb.String(), nil
}
// check if the big int overflows.
func bigIntOverflows(i *big.Int) bool {
// overflow is defined as i.BitLen() > MaxBitLen
// however this check can be expensive when doing many operations.
// So we first check if the word length is greater than maxWordLen.
// However the most significant word could be zero, hence we still do the bitlen check.
if len(i.Bits()) > maxWordLen {
return i.BitLen() > MaxBitLen
}
return false
}

View File

@ -43,6 +43,57 @@ func (s *intTestSuite) TestFromUint64() {
}
}
func (s *intTestSuite) TestNewIntFromBigInt() {
i := math.NewIntFromBigInt(nil)
s.Require().True(i.IsNil())
r := big.NewInt(42)
i = math.NewIntFromBigInt(r)
s.Require().Equal(r, i.BigInt())
// modify r and ensure i doesn't change
r = r.SetInt64(100)
s.Require().NotEqual(r, i.BigInt())
}
func (s *intTestSuite) TestNewIntFromBigIntMut() {
im := math.NewIntFromBigIntMut(nil)
s.Require().True(im.IsNil())
r := big.NewInt(42)
im = math.NewIntFromBigIntMut(r)
s.Require().Equal(r, im.BigInt())
// Compare value of NewIntFromBigInt and NewIntFromBigIntMut
i := math.NewIntFromBigInt(r)
s.Require().Equal(i, im)
// modify r and ensure i doesn't change & im changes
r = r.SetInt64(100)
s.Require().NotEqual(r, i.BigInt())
s.Require().Equal(r, im.BigInt())
}
func (s *intTestSuite) TestConvertToBigIntMutative() {
r := big.NewInt(42)
i := math.NewIntFromBigInt(r)
// Compare value of BigInt & BigIntMut
s.Require().Equal(i.BigInt(), i.BigIntMut())
// Modify BigIntMut() pointer and ensure i.BigIntMut() & i.BigInt() change
p := i.BigIntMut()
p.SetInt64(50)
s.Require().Equal(big.NewInt(50), i.BigIntMut())
s.Require().Equal(big.NewInt(50), i.BigInt())
// Modify big.Int() pointer and ensure i.BigIntMut() & i.BigInt() don't change
p = i.BigInt()
p.SetInt64(60)
s.Require().NotEqual(big.NewInt(60), i.BigIntMut())
s.Require().NotEqual(big.NewInt(60), i.BigInt())
}
func (s *intTestSuite) TestIntPanic() {
// Max Int = 2^256-1 = 1.1579209e+77
// Min Int = -(2^256-1) = -1.1579209e+77
@ -60,32 +111,66 @@ func (s *intTestSuite) TestIntPanic() {
s.Require().NotPanics(func() { i1.Add(i1) })
s.Require().NotPanics(func() { i2.Add(i2) })
s.Require().Panics(func() { i3.Add(i3) })
_, err := i1.SafeAdd(i1)
s.Require().Nil(err)
_, err = i2.SafeAdd(i2)
s.Require().Nil(err)
_, err = i3.SafeAdd(i3)
s.Require().Error(err)
s.Require().NotPanics(func() { i1.Sub(i1.Neg()) })
s.Require().NotPanics(func() { i2.Sub(i2.Neg()) })
s.Require().Panics(func() { i3.Sub(i3.Neg()) })
_, err = i1.SafeSub(i1.Neg())
s.Require().Nil(err)
_, err = i2.SafeSub(i2.Neg())
s.Require().Nil(err)
_, err = i3.SafeSub(i3.Neg())
s.Require().Error(err)
s.Require().Panics(func() { i1.Mul(i1) })
s.Require().Panics(func() { i2.Mul(i2) })
s.Require().Panics(func() { i3.Mul(i3) })
_, err = i1.SafeMul(i1)
s.Require().Error(err)
_, err = i2.SafeMul(i2)
s.Require().Error(err)
_, err = i3.SafeMul(i3)
s.Require().Error(err)
s.Require().Panics(func() { i1.Neg().Mul(i1.Neg()) })
s.Require().Panics(func() { i2.Neg().Mul(i2.Neg()) })
s.Require().Panics(func() { i3.Neg().Mul(i3.Neg()) })
_, err = i1.Neg().SafeMul(i1.Neg())
s.Require().Error(err)
_, err = i2.Neg().SafeMul(i2.Neg())
s.Require().Error(err)
_, err = i3.Neg().SafeMul(i3.Neg())
s.Require().Error(err)
// // Underflow check
// Underflow check
i3n := i3.Neg()
s.Require().NotPanics(func() { i3n.Sub(i1) })
s.Require().NotPanics(func() { i3n.Sub(i2) })
s.Require().Panics(func() { i3n.Sub(i3) })
_, err = i3n.SafeSub(i3)
s.Require().Error(err)
s.Require().NotPanics(func() { i3n.Add(i1.Neg()) })
s.Require().NotPanics(func() { i3n.Add(i2.Neg()) })
s.Require().Panics(func() { i3n.Add(i3.Neg()) })
_, err = i3n.SafeAdd(i3.Neg())
s.Require().Error(err)
s.Require().Panics(func() { i1.Mul(i1.Neg()) })
s.Require().Panics(func() { i2.Mul(i2.Neg()) })
s.Require().Panics(func() { i3.Mul(i3.Neg()) })
_, err = i1.SafeMul(i1.Neg())
s.Require().Error(err)
_, err = i2.SafeMul(i2.Neg())
s.Require().Error(err)
_, err = i3.SafeMul(i3.Neg())
s.Require().Error(err)
// Bound check
intmax := math.NewIntFromBigInt(new(big.Int).Sub(new(big.Int).Exp(big.NewInt(2), big.NewInt(256), nil), big.NewInt(1)))
@ -94,12 +179,18 @@ func (s *intTestSuite) TestIntPanic() {
s.Require().NotPanics(func() { intmin.Sub(math.ZeroInt()) })
s.Require().Panics(func() { intmax.Add(math.OneInt()) })
s.Require().Panics(func() { intmin.Sub(math.OneInt()) })
_, err = intmax.SafeAdd(math.OneInt())
s.Require().Error(err)
_, err = intmin.SafeSub(math.OneInt())
s.Require().Error(err)
s.Require().NotPanics(func() { math.NewIntFromBigInt(nil) })
s.Require().True(math.NewIntFromBigInt(nil).IsNil())
// Division-by-zero check
s.Require().Panics(func() { i1.Quo(math.NewInt(0)) })
_, err = i1.SafeQuo(math.NewInt(0))
s.Require().Error(err)
s.Require().NotPanics(func() { math.Int{}.BigInt() })
}
@ -408,7 +499,6 @@ func TestRoundTripMarshalToInt(t *testing.T) {
}
for _, value := range values {
value := value
t.Run(fmt.Sprintf("%d", value), func(t *testing.T) {
t.Parallel()
@ -457,7 +547,6 @@ func TestFormatIntNonDigits(t *testing.T) {
}
for _, value := range badCases {
value := value
t.Run(value, func(t *testing.T) {
s, err := math.FormatInt(value)
if err == nil {
@ -500,7 +589,6 @@ func TestFormatIntCorrectness(t *testing.T) {
}
for _, tt := range tests {
tt := tt
t.Run(tt.in, func(t *testing.T) {
got, err := math.FormatInt(tt.in)
if err != nil {
@ -513,3 +601,108 @@ func TestFormatIntCorrectness(t *testing.T) {
})
}
}
var sizeTests = []struct {
s string
want int
}{
{"", 1},
{"0", 1},
{"-0", 1},
{"-10", 3},
{"-10000", 6},
{"10000", 5},
{"100000", 6},
{"99999", 5},
{"9999999999", 10},
{"10000000000", 11},
{"99999999999", 11},
{"999999999999", 12},
{"9999999999999", 13},
{"99999999999999", 14},
{"999999999999999", 15},
{"1000000000000000", 16},
{"9999999999999999", 16},
{"99999999999999999", 17},
{"999999999999999999", 18},
{"-999999999999999999", 19},
{"9000000000000000000", 19},
{"-9999999999999990000", 20},
{"9999999999999990000", 19},
{"9999999999999999000", 19},
{"9999999999999999999", 19},
{"-9999999999999999999", 20},
{"18446744073709551616", 20},
{"18446744073709551618", 20},
{"184467440737095516181", 21},
{"100000000000000000000000", 24},
{"1000000000000000000000000000", 28},
{"9000000000099999999999999999", 28},
{"9999999999999999999999999999", 28},
{"9903520314283042199192993792", 28},
{"340282366920938463463374607431768211456", 39},
{"3402823669209384634633746074317682114569999", 43},
{"9999999999999999999999999999999999999999999", 43},
{"99999999999999999999999999999999999999999999", 44},
{"999999999999999999999999999999999999999999999", 45},
{"90000000000999999999999999999000000000099999999999999999", 56},
{"-90000000000999999999999999999000000000099999999999999999", 57},
{"9000000000099999999999999999900000000009999999999999999990", 58},
{"990000000009999999999999999990000000000999999999999999999999", 60},
{"99000000000999999999999999999000000000099999999999999999999919", 62},
{"90000000000999999990000000000000000000000000000000000000000000", 62},
{"99999999999999999999999999990000000000000000000000000000000000", 62},
{"11111111111111119999999999990000000000000000000000000000000000", 62},
{"99000000000999999999999999999000000000099999999999999999999919", 62},
{"10000000000000000000000000000000000000000000000000000000000000", 62},
{"10000000000000000000000000000000000000000000000000000000000000000000000000000", 77},
{"99999999999999999999999999999999999999999999999999999999999999999999999999999", 77},
{"110000000000000000000000000000000000000000000000000000000000000000000000000009", 78},
}
func TestNewIntFromString(t *testing.T) {
for _, st := range sizeTests {
ii, _ := math.NewIntFromString(st.s)
require.Equal(t, st.want, ii.Size(), "size mismatch for %q", st.s)
}
}
func BenchmarkIntSize(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for _, st := range sizeTests {
ii, _ := math.NewIntFromString(st.s)
got := ii.Size()
if got != st.want {
b.Errorf("%q:: got=%d, want=%d", st.s, got, st.want)
}
sink = got
}
}
if sink == nil {
b.Fatal("Benchmark did not run!")
}
sink = nil
}
func BenchmarkIntOverflowCheckTime(b *testing.B) {
ints := []*big.Int{}
for _, st := range sizeTests {
ii, _ := math.NewIntFromString(st.s)
ints = append(ints, ii.BigInt())
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for j := range sizeTests {
got := math.NewIntFromBigIntMut(ints[j])
sink = got
}
}
if sink == nil {
b.Fatal("Benchmark did not run!")
}
sink = nil
}

971
math/legacy_dec.go Normal file
View File

@ -0,0 +1,971 @@
package math
import (
"encoding/json"
"errors"
"fmt"
"math/big"
"strconv"
"strings"
"testing"
)
// LegacyDec NOTE: never use new(Dec) or else we will panic unmarshalling into the
// nil embedded big.Int
type LegacyDec struct {
i *big.Int
}
const (
// LegacyPrecision number of decimal places
LegacyPrecision = 18
// LegacyDecimalPrecisionBits bits required to represent the above precision
// Ceiling[Log2[10^Precision - 1]]
// Deprecated: This is unused and will be removed
LegacyDecimalPrecisionBits = 60
// maxApproxRootIterations max number of iterations in ApproxRoot function
maxApproxRootIterations = 300
)
var (
precisionReuse = new(big.Int).Exp(big.NewInt(10), big.NewInt(LegacyPrecision), nil)
fivePrecision = new(big.Int).Quo(precisionReuse, big.NewInt(2))
upperLimit LegacyDec
lowerLimit LegacyDec
precisionMultipliers []*big.Int
zeroInt = big.NewInt(0)
oneInt = big.NewInt(1)
tenInt = big.NewInt(10)
smallestDec = LegacySmallestDec()
)
// Decimal errors
var (
ErrLegacyEmptyDecimalStr = errors.New("decimal string cannot be empty")
ErrLegacyInvalidDecimalLength = errors.New("invalid decimal length")
ErrLegacyInvalidDecimalStr = errors.New("invalid decimal string")
)
// Set precision multipliers
func init() {
precisionMultipliers = make([]*big.Int, LegacyPrecision+1)
for i := 0; i <= LegacyPrecision; i++ {
precisionMultipliers[i] = calcPrecisionMultiplier(int64(i))
}
// 2^256 * 10^18 -1
tmp := new(big.Int).Exp(big.NewInt(2), big.NewInt(256), nil)
tmp = new(big.Int).Sub(new(big.Int).Mul(tmp, precisionReuse), big.NewInt(1))
upperLimit = LegacyNewDecFromBigIntWithPrec(tmp, LegacyPrecision)
lowerLimit = upperLimit.Neg()
}
func precisionInt() *big.Int {
return new(big.Int).Set(precisionReuse)
}
func LegacyZeroDec() LegacyDec { return LegacyDec{new(big.Int).Set(zeroInt)} }
func LegacyOneDec() LegacyDec { return LegacyDec{precisionInt()} }
func LegacySmallestDec() LegacyDec { return LegacyDec{new(big.Int).Set(oneInt)} }
// calculate the precision multiplier
func calcPrecisionMultiplier(prec int64) *big.Int {
if prec < 0 {
panic(fmt.Sprintf("negative precision %v", prec))
}
if prec > LegacyPrecision {
panic(fmt.Sprintf("too much precision, maximum %v, provided %v", LegacyPrecision, prec))
}
zerosToAdd := LegacyPrecision - prec
multiplier := new(big.Int).Exp(tenInt, big.NewInt(zerosToAdd), nil)
return multiplier
}
// get the precision multiplier, do not mutate result
func precisionMultiplier(prec int64) *big.Int {
if prec < 0 {
panic(fmt.Sprintf("negative precision %v", prec))
}
if prec > LegacyPrecision {
panic(fmt.Sprintf("too much precision, maximum %v, provided %v", LegacyPrecision, prec))
}
return precisionMultipliers[prec]
}
// LegacyNewDec create a new Dec from integer assuming whole number
func LegacyNewDec(i int64) LegacyDec {
return LegacyNewDecWithPrec(i, 0)
}
// LegacyNewDecWithPrec create a new Dec from integer with decimal place at prec
// CONTRACT: prec <= Precision
func LegacyNewDecWithPrec(i, prec int64) LegacyDec {
bi := big.NewInt(i)
return LegacyDec{
bi.Mul(bi, precisionMultiplier(prec)),
}
}
// LegacyNewDecFromBigInt create a new Dec from big integer assuming whole numbers
// CONTRACT: prec <= Precision
func LegacyNewDecFromBigInt(i *big.Int) LegacyDec {
return LegacyNewDecFromBigIntWithPrec(i, 0)
}
// LegacyNewDecFromBigIntWithPrec create a new Dec from big integer assuming whole numbers
// CONTRACT: prec <= Precision
func LegacyNewDecFromBigIntWithPrec(i *big.Int, prec int64) LegacyDec {
return LegacyDec{
new(big.Int).Mul(i, precisionMultiplier(prec)),
}
}
// LegacyNewDecFromInt create a new Dec from big integer assuming whole numbers
// CONTRACT: prec <= Precision
func LegacyNewDecFromInt(i Int) LegacyDec {
return LegacyNewDecFromIntWithPrec(i, 0)
}
// LegacyNewDecFromIntWithPrec create a new Dec from big integer with decimal place at prec
// CONTRACT: prec <= Precision
func LegacyNewDecFromIntWithPrec(i Int, prec int64) LegacyDec {
return LegacyDec{
new(big.Int).Mul(i.BigIntMut(), precisionMultiplier(prec)),
}
}
// LegacyNewDecFromStr create a decimal from an input decimal string.
// valid must come in the form:
//
// (-) whole integers (.) decimal integers
//
// examples of acceptable input include:
//
// -123.456
// 456.7890
// 345
// -456789
//
// NOTE - An error will return if more decimal places
// are provided in the string than the constant Precision.
//
// CONTRACT - This function does not mutate the input str.
func LegacyNewDecFromStr(str string) (LegacyDec, error) {
// first extract any negative symbol
neg := false
if len(str) > 0 && str[0] == '-' {
neg = true
str = str[1:]
}
if len(str) == 0 {
return LegacyDec{}, ErrLegacyEmptyDecimalStr
}
strs := strings.Split(str, ".")
lenDecs := 0
combinedStr := strs[0]
if len(strs) == 2 { // has a decimal place
lenDecs = len(strs[1])
if lenDecs == 0 || len(combinedStr) == 0 {
return LegacyDec{}, ErrLegacyInvalidDecimalLength
}
combinedStr += strs[1]
} else if len(strs) > 2 {
return LegacyDec{}, ErrLegacyInvalidDecimalStr
}
if lenDecs > LegacyPrecision {
return LegacyDec{}, fmt.Errorf("value '%s' exceeds max precision by %d decimal places: max precision %d", str, LegacyPrecision-lenDecs, LegacyPrecision)
}
// add some extra zero's to correct to the Precision factor
zerosToAdd := LegacyPrecision - lenDecs
zeros := strings.Repeat("0", zerosToAdd)
combinedStr += zeros
combined, ok := new(big.Int).SetString(combinedStr, 10) // base 10
if !ok {
return LegacyDec{}, fmt.Errorf("failed to set decimal string with base 10: %s", combinedStr)
}
if neg {
combined = new(big.Int).Neg(combined)
}
result := LegacyDec{i: combined}
if !result.IsInValidRange() {
return LegacyDec{}, fmt.Errorf("out of range: %w", ErrLegacyInvalidDecimalStr)
}
return result, nil
}
// LegacyMustNewDecFromStr Decimal from string, panic on error
func LegacyMustNewDecFromStr(s string) LegacyDec {
dec, err := LegacyNewDecFromStr(s)
if err != nil {
panic(err)
}
return dec
}
func (d LegacyDec) IsNil() bool { return d.i == nil } // is decimal nil
func (d LegacyDec) IsZero() bool { return (d.i).Sign() == 0 } // is equal to zero
func (d LegacyDec) IsNegative() bool { return (d.i).Sign() == -1 } // is negative
func (d LegacyDec) IsPositive() bool { return (d.i).Sign() == 1 } // is positive
func (d LegacyDec) Equal(d2 LegacyDec) bool { return (d.i).Cmp(d2.i) == 0 } // equal decimals
func (d LegacyDec) GT(d2 LegacyDec) bool { return (d.i).Cmp(d2.i) > 0 } // greater than
func (d LegacyDec) GTE(d2 LegacyDec) bool { return (d.i).Cmp(d2.i) >= 0 } // greater than or equal
func (d LegacyDec) LT(d2 LegacyDec) bool { return (d.i).Cmp(d2.i) < 0 } // less than
func (d LegacyDec) LTE(d2 LegacyDec) bool { return (d.i).Cmp(d2.i) <= 0 } // less than or equal
func (d LegacyDec) Neg() LegacyDec { return LegacyDec{new(big.Int).Neg(d.i)} } // reverse the decimal sign
func (d LegacyDec) NegMut() LegacyDec { d.i.Neg(d.i); return d } // reverse the decimal sign, mutable
func (d LegacyDec) Abs() LegacyDec { return LegacyDec{new(big.Int).Abs(d.i)} } // absolute value
func (d LegacyDec) AbsMut() LegacyDec { d.i.Abs(d.i); return d } // absolute value, mutable
func (d LegacyDec) Set(d2 LegacyDec) LegacyDec { d.i.Set(d2.i); return d } // set to existing dec value
func (d LegacyDec) Clone() LegacyDec { return LegacyDec{new(big.Int).Set(d.i)} } // clone new dec
// BigInt returns a copy of the underlying big.Int.
func (d LegacyDec) BigInt() *big.Int {
if d.IsNil() {
return nil
}
cp := new(big.Int)
return cp.Set(d.i)
}
// BigIntMut converts LegacyDec to big.Int, mutative the input
func (d LegacyDec) BigIntMut() *big.Int {
if d.IsNil() {
return nil
}
return d.i
}
func (d LegacyDec) ImmutOp(op func(LegacyDec, LegacyDec) LegacyDec, d2 LegacyDec) LegacyDec {
return op(d.Clone(), d2)
}
func (d LegacyDec) ImmutOpInt(op func(LegacyDec, Int) LegacyDec, d2 Int) LegacyDec {
return op(d.Clone(), d2)
}
func (d LegacyDec) ImmutOpInt64(op func(LegacyDec, int64) LegacyDec, d2 int64) LegacyDec {
// TODO: use already allocated operand bigint to avoid
// newint each time, add mutex for race condition
// Issue: https://github.com/cosmos/cosmos-sdk/issues/11166
return op(d.Clone(), d2)
}
func (d LegacyDec) SetInt64(i int64) LegacyDec {
d.i.SetInt64(i)
d.i.Mul(d.i, precisionReuse)
return d
}
// Add addition
func (d LegacyDec) Add(d2 LegacyDec) LegacyDec {
return d.ImmutOp(LegacyDec.AddMut, d2)
}
// AddMut mutable addition
func (d LegacyDec) AddMut(d2 LegacyDec) LegacyDec {
d.i.Add(d.i, d2.i)
d.assertInValidRange()
return d
}
// Sub subtraction
func (d LegacyDec) Sub(d2 LegacyDec) LegacyDec {
return d.ImmutOp(LegacyDec.SubMut, d2)
}
// SubMut mutable subtraction
func (d LegacyDec) SubMut(d2 LegacyDec) LegacyDec {
d.i.Sub(d.i, d2.i)
d.assertInValidRange()
return d
}
func (d LegacyDec) assertInValidRange() {
if !d.IsInValidRange() {
panic("Int overflow")
}
}
// IsInValidRange returns true when the value is between the upper limit of (2^256 * 10^18)
// and the lower limit of -1*(2^256 * 10^18).
func (d LegacyDec) IsInValidRange() bool {
return !(d.GT(upperLimit) || d.LT(lowerLimit))
}
// Mul multiplication
func (d LegacyDec) Mul(d2 LegacyDec) LegacyDec {
return d.ImmutOp(LegacyDec.MulMut, d2)
}
// MulMut mutable multiplication
func (d LegacyDec) MulMut(d2 LegacyDec) LegacyDec {
d.i.Mul(d.i, d2.i)
chopped := chopPrecisionAndRound(d.i)
*d.i = *chopped
d.assertInValidRange()
return d
}
// MulTruncate multiplication truncate
func (d LegacyDec) MulTruncate(d2 LegacyDec) LegacyDec {
return d.ImmutOp(LegacyDec.MulTruncateMut, d2)
}
// MulTruncateMut mutable multiplication truncate
func (d LegacyDec) MulTruncateMut(d2 LegacyDec) LegacyDec {
d.i.Mul(d.i, d2.i)
chopPrecisionAndTruncate(d.i)
d.assertInValidRange()
return d
}
// MulRoundUp multiplication round up at precision end.
func (d LegacyDec) MulRoundUp(d2 LegacyDec) LegacyDec {
return d.ImmutOp(LegacyDec.MulRoundUpMut, d2)
}
// MulRoundUpMut mutable multiplication with round up at precision end.
func (d LegacyDec) MulRoundUpMut(d2 LegacyDec) LegacyDec {
d.i.Mul(d.i, d2.i)
chopPrecisionAndRoundUp(d.i)
d.assertInValidRange()
return d
}
// MulInt multiplication
func (d LegacyDec) MulInt(i Int) LegacyDec {
return d.ImmutOpInt(LegacyDec.MulIntMut, i)
}
func (d LegacyDec) MulIntMut(i Int) LegacyDec {
d.i.Mul(d.i, i.BigIntMut())
d.assertInValidRange()
return d
}
// MulInt64 multiplication with int64
func (d LegacyDec) MulInt64(i int64) LegacyDec {
return d.ImmutOpInt64(LegacyDec.MulInt64Mut, i)
}
func (d LegacyDec) MulInt64Mut(i int64) LegacyDec {
d.i.Mul(d.i, big.NewInt(i))
d.assertInValidRange()
return d
}
// Quo quotient
func (d LegacyDec) Quo(d2 LegacyDec) LegacyDec {
return d.ImmutOp(LegacyDec.QuoMut, d2)
}
var squaredPrecisionReuse = new(big.Int).Mul(precisionReuse, precisionReuse)
// QuoMut mutable quotient
func (d LegacyDec) QuoMut(d2 LegacyDec) LegacyDec {
// multiply by precision twice
d.i.Mul(d.i, squaredPrecisionReuse)
d.i.Quo(d.i, d2.i)
chopPrecisionAndRound(d.i)
d.assertInValidRange()
return d
}
// QuoTruncate quotient truncate
func (d LegacyDec) QuoTruncate(d2 LegacyDec) LegacyDec {
return d.ImmutOp(LegacyDec.QuoTruncateMut, d2)
}
// QuoTruncateMut divides the current LegacyDec value by the provided LegacyDec value, truncating the result.
func (d LegacyDec) QuoTruncateMut(d2 LegacyDec) LegacyDec {
// multiply precision once before performing division
d.i.Mul(d.i, precisionReuse)
d.i.Quo(d.i, d2.i)
d.assertInValidRange()
return d
}
// QuoRoundUp quotient, round up
func (d LegacyDec) QuoRoundUp(d2 LegacyDec) LegacyDec {
return d.ImmutOp(LegacyDec.QuoRoundupMut, d2)
}
// QuoRoundupMut mutable quotient, round up
func (d LegacyDec) QuoRoundupMut(d2 LegacyDec) LegacyDec {
// multiply precision twice
d.i.Mul(d.i, precisionReuse)
_, rem := d.i.QuoRem(d.i, d2.i, big.NewInt(0))
if rem.Sign() > 0 && d.IsNegative() == d2.IsNegative() ||
rem.Sign() < 0 && d.IsNegative() != d2.IsNegative() {
d.i.Add(d.i, oneInt)
}
d.assertInValidRange()
return d
}
// QuoInt quotient
func (d LegacyDec) QuoInt(i Int) LegacyDec {
return d.ImmutOpInt(LegacyDec.QuoIntMut, i)
}
func (d LegacyDec) QuoIntMut(i Int) LegacyDec {
d.i.Quo(d.i, i.BigIntMut())
return d
}
// QuoInt64 quotient with int64
func (d LegacyDec) QuoInt64(i int64) LegacyDec {
return d.ImmutOpInt64(LegacyDec.QuoInt64Mut, i)
}
func (d LegacyDec) QuoInt64Mut(i int64) LegacyDec {
d.i.Quo(d.i, big.NewInt(i))
return d
}
// ApproxRoot returns an approximate estimation of a Dec's positive real nth root
// using Newton's method (where n is positive). The algorithm starts with some guess and
// computes the sequence of improved guesses until an answer converges to an
// approximate answer. It returns `|d|.ApproxRoot() * -1` if input is negative.
// A maximum number of 100 iterations is used a backup boundary condition for
// cases where the answer never converges enough to satisfy the main condition.
func (d LegacyDec) ApproxRoot(root uint64) (guess LegacyDec, err error) {
defer func() {
if r := recover(); r != nil {
var ok bool
err, ok = r.(error)
if !ok {
err = errors.New("out of bounds")
}
}
}()
if root == 0 {
// Return 1 as root 0 of any number is considered 1.
return LegacyOneDec(), nil
}
if d.IsNegative() {
absRoot, err := d.Neg().ApproxRoot(root)
return absRoot.NegMut(), err
}
// Direct return for base cases: d^1 = d or when d is 0 or 1.
scratchOneDec := LegacyOneDec()
if root == 1 || d.IsZero() || d.Equal(scratchOneDec) {
return d, nil
}
guess, delta := scratchOneDec, LegacyOneDec()
for iter := 0; iter < maxApproxRootIterations; iter++ {
prev := guess.Power(root - 1)
if prev.IsZero() {
prev = smallestDec
}
// Compute delta = (d/prev - guess) / root
delta.Set(d).QuoMut(prev)
delta.SubMut(guess)
delta.QuoInt64Mut(int64(root))
guess.AddMut(delta)
// Stop when delta is small enough
if delta.Abs().LTE(smallestDec) {
break
}
}
return guess, nil
}
// Power returns the result of raising to a positive integer power
func (d LegacyDec) Power(power uint64) LegacyDec {
res := LegacyDec{new(big.Int).Set(d.i)}
return res.PowerMut(power)
}
func (d LegacyDec) PowerMut(power uint64) LegacyDec {
if power == 0 {
// Set to 1 with the correct precision.
d.i.Set(precisionReuse)
return d
}
tmp := LegacyOneDec()
for i := power; i > 1; {
if i%2 != 0 {
tmp.MulMut(d)
}
i /= 2
d.MulMut(d)
}
return d.MulMut(tmp)
}
// ApproxSqrt is a wrapper around ApproxRoot for the common special case
// of finding the square root of a number. It returns -(sqrt(abs(d)) if input is negative.
func (d LegacyDec) ApproxSqrt() (LegacyDec, error) {
return d.ApproxRoot(2)
}
// IsInteger is integer, e.g. decimals are zero
func (d LegacyDec) IsInteger() bool {
return new(big.Int).Rem(d.i, precisionReuse).Sign() == 0
}
// Format format decimal state
func (d LegacyDec) Format(s fmt.State, verb rune) {
_, err := s.Write([]byte(d.String()))
if err != nil {
panic(err)
}
}
func (d LegacyDec) String() string {
if d.i == nil {
return d.i.String()
}
isNeg := d.IsNegative()
if isNeg {
d = d.Neg()
}
bzInt, err := d.i.MarshalText()
if err != nil {
return ""
}
inputSize := len(bzInt)
var bzStr []byte
// TODO: Remove trailing zeros
// case 1, purely decimal
if inputSize <= LegacyPrecision {
bzStr = make([]byte, LegacyPrecision+2)
// 0. prefix
bzStr[0] = byte('0')
bzStr[1] = byte('.')
// set relevant digits to 0
for i := 0; i < LegacyPrecision-inputSize; i++ {
bzStr[i+2] = byte('0')
}
// set final digits
copy(bzStr[2+(LegacyPrecision-inputSize):], bzInt)
} else {
// inputSize + 1 to account for the decimal point that is being added
bzStr = make([]byte, inputSize+1)
decPointPlace := inputSize - LegacyPrecision
copy(bzStr, bzInt[:decPointPlace]) // pre-decimal digits
bzStr[decPointPlace] = byte('.') // decimal point
copy(bzStr[decPointPlace+1:], bzInt[decPointPlace:]) // post-decimal digits
}
if isNeg {
return "-" + string(bzStr)
}
return string(bzStr)
}
// Float64 returns the float64 representation of a Dec.
// Will return the error if the conversion failed.
func (d LegacyDec) Float64() (float64, error) {
return strconv.ParseFloat(d.String(), 64)
}
// MustFloat64 returns the float64 representation of a Dec.
// Would panic if the conversion failed.
func (d LegacyDec) MustFloat64() float64 {
if value, err := strconv.ParseFloat(d.String(), 64); err != nil {
panic(err)
} else {
return value
}
}
// ____
// __| |__ "chop 'em
// ` \ round!"
// ___|| ~ _ -bankers
// | | __
// | | | __|__|__
// |_____: / | $$$ |
// |________|
// Remove a Precision amount of rightmost digits and perform bankers rounding
// on the remainder (gaussian rounding) on the digits which have been removed.
//
// Mutates the input. Use the non-mutative version if that is undesired
func chopPrecisionAndRound(d *big.Int) *big.Int {
// remove the negative and add it back when returning
if d.Sign() == -1 {
// make d positive, compute chopped value, and then un-mutate d
d = d.Neg(d)
d = chopPrecisionAndRound(d)
d = d.Neg(d)
return d
}
// get the truncated quotient and remainder
quo, rem := d, big.NewInt(0)
quo, rem = quo.QuoRem(d, precisionReuse, rem)
if rem.Sign() == 0 { // remainder is zero
return quo
}
switch rem.Cmp(fivePrecision) {
case -1:
return quo
case 1:
return quo.Add(quo, oneInt)
default: // bankers rounding must take place
// always round to an even number
if quo.Bit(0) == 0 {
return quo
}
return quo.Add(quo, oneInt)
}
}
func chopPrecisionAndRoundUp(d *big.Int) *big.Int {
// remove the negative and add it back when returning
if d.Sign() == -1 {
// make d positive, compute chopped value, and then un-mutate d
d = d.Neg(d)
// truncate since d is negative...
chopPrecisionAndTruncate(d)
d = d.Neg(d)
return d
}
// get the truncated quotient and remainder
quo, rem := d, big.NewInt(0)
quo, rem = quo.QuoRem(d, precisionReuse, rem)
if rem.Sign() == 0 { // remainder is zero
return quo
}
return quo.Add(quo, oneInt)
}
func chopPrecisionAndRoundNonMutative(d *big.Int) *big.Int {
tmp := new(big.Int).Set(d)
return chopPrecisionAndRound(tmp)
}
// RoundInt64 rounds the decimal using bankers rounding
func (d LegacyDec) RoundInt64() int64 {
chopped := chopPrecisionAndRoundNonMutative(d.i)
if !chopped.IsInt64() {
panic("Int64() out of bound")
}
return chopped.Int64()
}
// RoundInt round the decimal using bankers rounding
func (d LegacyDec) RoundInt() Int {
return NewIntFromBigIntMut(chopPrecisionAndRoundNonMutative(d.i))
}
// chopPrecisionAndTruncate is similar to chopPrecisionAndRound,
// but always rounds down. It does not mutate the input.
func chopPrecisionAndTruncate(d *big.Int) {
d.Quo(d, precisionReuse)
}
func chopPrecisionAndTruncateNonMutative(d *big.Int) *big.Int {
tmp := new(big.Int).Set(d)
chopPrecisionAndTruncate(tmp)
return tmp
}
// TruncateInt64 truncates the decimals from the number and returns an int64
func (d LegacyDec) TruncateInt64() int64 {
chopped := chopPrecisionAndTruncateNonMutative(d.i)
if !chopped.IsInt64() {
panic("Int64() out of bound")
}
return chopped.Int64()
}
// TruncateInt truncates the decimals from the number and returns an Int
func (d LegacyDec) TruncateInt() Int {
return NewIntFromBigIntMut(chopPrecisionAndTruncateNonMutative(d.i))
}
// TruncateDec truncates the decimals from the number and returns a Dec
func (d LegacyDec) TruncateDec() LegacyDec {
return LegacyNewDecFromBigInt(chopPrecisionAndTruncateNonMutative(d.i))
}
// Ceil returns the smallest integer value (as a decimal) that is greater than
// or equal to the given decimal.
func (d LegacyDec) Ceil() LegacyDec {
tmp := new(big.Int).Set(d.i)
quo, rem := tmp, big.NewInt(0)
quo, rem = quo.QuoRem(tmp, precisionReuse, rem)
// no need to round with a zero remainder regardless of sign
var r LegacyDec
switch rem.Sign() {
case 0:
r = LegacyNewDecFromBigInt(quo)
case -1:
r = LegacyNewDecFromBigInt(quo)
default:
r = LegacyNewDecFromBigInt(quo.Add(quo, oneInt))
}
r.assertInValidRange()
return r
}
// LegacyMaxSortableDec is the largest Dec that can be passed into SortableDecBytes()
// Its negative form is the least Dec that can be passed in.
var LegacyMaxSortableDec LegacyDec
func init() {
LegacyMaxSortableDec = LegacyOneDec().Quo(LegacySmallestDec())
}
// LegacyValidSortableDec ensures that a Dec is within the sortable bounds,
// a Dec can't have a precision of less than 10^-18.
// Max sortable decimal was set to the reciprocal of SmallestDec.
func LegacyValidSortableDec(dec LegacyDec) bool {
return dec.Abs().LTE(LegacyMaxSortableDec)
}
// LegacySortableDecBytes returns a byte slice representation of a Dec that can be sorted.
// Left and right pads with 0s so there are 18 digits to left and right of the decimal point.
// For this reason, there is a maximum and minimum value for this, enforced by ValidSortableDec.
func LegacySortableDecBytes(dec LegacyDec) []byte {
if !LegacyValidSortableDec(dec) {
panic("dec must be within bounds")
}
// Instead of adding an extra byte to all sortable decs in order to handle max sortable, we just
// makes its bytes be "max" which comes after all numbers in ASCIIbetical order
if dec.Equal(LegacyMaxSortableDec) {
return []byte("max")
}
// For the same reason, we make the bytes of minimum sortable dec be --, which comes before all numbers.
if dec.Equal(LegacyMaxSortableDec.Neg()) {
return []byte("--")
}
// We move the negative sign to the front of all the left padded 0s, to make negative numbers come before positive numbers
if dec.IsNegative() {
return append([]byte("-"), []byte(fmt.Sprintf(fmt.Sprintf("%%0%ds", LegacyPrecision*2+1), dec.Abs().String()))...)
}
return []byte(fmt.Sprintf(fmt.Sprintf("%%0%ds", LegacyPrecision*2+1), dec.String()))
}
// reuse nil values
var nilJSON []byte
func init() {
empty := new(big.Int)
bz, _ := empty.MarshalText()
nilJSON, _ = json.Marshal(string(bz))
}
// MarshalJSON marshals the decimal
func (d LegacyDec) MarshalJSON() ([]byte, error) {
if d.i == nil {
return nilJSON, nil
}
return json.Marshal(d.String())
}
// UnmarshalJSON defines custom decoding scheme
func (d *LegacyDec) UnmarshalJSON(bz []byte) error {
if d.i == nil {
d.i = new(big.Int)
}
var text string
err := json.Unmarshal(bz, &text)
if err != nil {
return err
}
// TODO: Reuse dec allocation
newDec, err := LegacyNewDecFromStr(text)
if err != nil {
return err
}
d.i = newDec.i
return nil
}
// MarshalYAML returns the YAML representation.
func (d LegacyDec) MarshalYAML() (interface{}, error) {
return d.String(), nil
}
// Marshal implements the gogo proto custom type interface.
func (d LegacyDec) Marshal() ([]byte, error) {
i := d.i
if i == nil {
i = new(big.Int)
}
return i.MarshalText()
}
// MarshalTo implements the gogo proto custom type interface.
func (d *LegacyDec) MarshalTo(data []byte) (n int, err error) {
i := d.i
if i == nil {
i = new(big.Int)
}
if i.Sign() == 0 {
copy(data, []byte{0x30})
return 1, nil
}
bz, err := d.Marshal()
if err != nil {
return 0, err
}
copy(data, bz)
return len(bz), nil
}
// Unmarshal implements the gogo proto custom type interface.
func (d *LegacyDec) Unmarshal(data []byte) error {
if len(data) == 0 {
d = nil
return nil
}
if d.i == nil {
d.i = new(big.Int)
}
if err := d.i.UnmarshalText(data); err != nil {
return err
}
if !d.IsInValidRange() {
return errors.New("decimal out of range")
}
return nil
}
// Size implements the gogo proto custom type interface.
func (d *LegacyDec) Size() int {
bz, _ := d.Marshal()
return len(bz)
}
// MarshalAmino Override Amino binary serialization by proxying to protobuf.
func (d LegacyDec) MarshalAmino() ([]byte, error) { return d.Marshal() }
func (d *LegacyDec) UnmarshalAmino(bz []byte) error { return d.Unmarshal(bz) }
// helpers
// LegacyDecsEqual return true if two decimal arrays are equal.
func LegacyDecsEqual(d1s, d2s []LegacyDec) bool {
if len(d1s) != len(d2s) {
return false
}
for i, d1 := range d1s {
if !d1.Equal(d2s[i]) {
return false
}
}
return true
}
// LegacyMinDec minimum decimal between two
func LegacyMinDec(d1, d2 LegacyDec) LegacyDec {
if d1.LT(d2) {
return d1
}
return d2
}
// LegacyMaxDec maximum decimal between two
func LegacyMaxDec(d1, d2 LegacyDec) LegacyDec {
if d1.LT(d2) {
return d2
}
return d1
}
// LegacyDecEq intended to be used with require/assert: require.True(DecEq(...))
func LegacyDecEq(t *testing.T, exp, got LegacyDec) (*testing.T, bool, string, string, string) {
t.Helper()
return t, exp.Equal(got), "expected:\t%v\ngot:\t\t%v", exp.String(), got.String()
}
func LegacyDecApproxEq(t *testing.T, d1, d2, tol LegacyDec) (*testing.T, bool, string, string, string) {
t.Helper()
diff := d1.Sub(d2).Abs()
return t, diff.LTE(tol), "expected |d1 - d2| <:\t%v\ngot |d1 - d2| = \t\t%v", tol.String(), diff.String()
}
// FormatDec formats a decimal (as encoded in protobuf) into a value-rendered
// string following ADR-050. This function operates with string manipulation
// (instead of manipulating the sdk.Dec object).
func FormatDec(v string) (string, error) {
parts := strings.Split(v, ".")
if len(parts) > 2 {
return "", fmt.Errorf("invalid decimal: too many points in %s", v)
}
intPart, err := FormatInt(parts[0])
if err != nil {
return "", err
}
if len(parts) == 1 {
return intPart, nil
}
decPart := strings.TrimRight(parts[1], "0")
if len(decPart) == 0 {
return intPart, nil
}
// Ensure that the decimal part has only digits.
// https://github.com/cosmos/cosmos-sdk/issues/12811
if !hasOnlyDigits(decPart) {
return "", fmt.Errorf("non-digits detected after decimal point in: %q", decPart)
}
return intPart + "." + decPart, nil
}

View File

@ -22,9 +22,3 @@ func FuzzLegacyNewDecFromStr(f *testing.F) {
}
})
}
func TestDecNegativePrecision(t *testing.T) {
t.Skip("https://github.com/cosmos/cosmos-sdk/issues/14004 is not yet addressed")
LegacyNewDecWithPrec(10, -1)
}

View File

@ -90,7 +90,6 @@ func (s *decimalInternalTestSuite) TestDecMarshalJSON() {
{"12340Int", LegacyNewDec(12340), "\"12340.000000000000000000\"", false},
}
for _, tt := range tests {
tt := tt
s.T().Run(tt.name, func(t *testing.T) {
got, err := tt.d.MarshalJSON()
if (err != nil) != tt.wantErr {

1328
math/legacy_dec_test.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,6 @@
package math
import (
"cmp"
)
import "cmp"
func Max[T cmp.Ordered](a, b T, rest ...T) T {
max := a

View File

@ -0,0 +1,16 @@
sonar.projectKey=cosmos-sdk-math
sonar.organization=cosmos
sonar.projectName=Cosmos SDK - Math
sonar.project.monorepo.enabled=true
sonar.sources=.
sonar.exclusions=**/*_test.go,**/*.pb.go,**/*.pulsar.go,**/*.pb.gw.go
sonar.coverage.exclusions=**/*_test.go,**/testutil/**,**/*.pb.go,**/*.pb.gw.go,**/*.pulsar.go,test_helpers.go,docs/**
sonar.tests=.
sonar.test.inclusions=**/*_test.go
sonar.go.coverage.reportPaths=coverage.out
sonar.sourceEncoding=UTF-8
sonar.scm.provider=git
sonar.scm.forceReloadAll=true

View File

@ -15,24 +15,36 @@ type Uint struct {
// BigInt converts Uint to big.Int
func (u Uint) BigInt() *big.Int {
if u.IsNil() {
return nil
}
return new(big.Int).Set(u.i)
}
// BigIntMut converts Uint to big.Int, mutative the input
func (u Uint) BigIntMut() *big.Int {
if u.IsNil() {
return nil
}
return u.i
}
// IsNil returns true if Uint is uninitialized
func (u Uint) IsNil() bool {
return u.i == nil
}
// NewUintFromBigUint constructs Uint from big.Uint
// NewUintFromBigInt constructs Uint from big.Int
// Panics if i is negative or wider than 256 bits
func NewUintFromBigInt(i *big.Int) Uint {
u, err := checkNewUint(i)
if err != nil {
panic(fmt.Errorf("overflow: %s", err))
panic(fmt.Errorf("overflow: %w", err))
}
return u
}
// NewUint constructs Uint from int64
// NewUint constructs Uint from uint64
func NewUint(n uint64) Uint {
i := new(big.Int)
i.SetUint64(n)
@ -40,6 +52,7 @@ func NewUint(n uint64) Uint {
}
// NewUintFromString constructs Uint from string
// Panics if parsed s is negative or wider than 256 bits
func NewUintFromString(s string) Uint {
u, err := ParseUint(s)
if err != nil {
@ -86,7 +99,7 @@ func (u Uint) LTE(u2 Uint) bool { return !u.GT(u2) }
// Add adds Uint from another
func (u Uint) Add(u2 Uint) Uint { return NewUintFromBigInt(new(big.Int).Add(u.i, u2.i)) }
// Add convert uint64 and add it to Uint
// AddUint64 convert uint64 and add it to Uint
func (u Uint) AddUint64(u2 uint64) Uint { return u.Add(NewUint(u2)) }
// Sub adds Uint from another
@ -100,13 +113,14 @@ func (u Uint) Mul(u2 Uint) (res Uint) {
return NewUintFromBigInt(new(big.Int).Mul(u.i, u2.i))
}
// Mul multiplies two Uints
// MulUint64 multiplies two Uints
func (u Uint) MulUint64(u2 uint64) (res Uint) { return u.Mul(NewUint(u2)) }
// Quo divides Uint with Uint
func (u Uint) Quo(u2 Uint) (res Uint) { return NewUintFromBigInt(div(u.i, u2.i)) }
// Mod returns remainder after dividing with Uint
// Panics if u2 is zero
func (u Uint) Mod(u2 Uint) Uint {
if u2.IsZero() {
panic("division-by-zero")
@ -125,16 +139,16 @@ func (u Uint) Decr() Uint {
return u.Sub(OneUint())
}
// Quo divides Uint with uint64
// QuoUint64 divides Uint with uint64
func (u Uint) QuoUint64(u2 uint64) Uint { return u.Quo(NewUint(u2)) }
// Return the minimum of the Uints
// MinUint returns the minimum of the Uints
func MinUint(u1, u2 Uint) Uint { return NewUintFromBigInt(min(u1.i, u2.i)) }
// Return the maximum of the Uints
// MaxUint returns the maximum of the Uints
func MaxUint(u1, u2 Uint) Uint { return NewUintFromBigInt(max(u1.i, u2.i)) }
// Human readable string
// String returns human-readable string
func (u Uint) String() string { return u.i.String() }
// MarshalJSON defines custom encoding scheme
@ -205,7 +219,7 @@ func (u *Uint) Size() int {
return len(bz)
}
// Override Amino binary serialization by proxying to protobuf.
// MarshalAmino override Amino binary serialization by proxying to protobuf.
func (u Uint) MarshalAmino() ([]byte, error) { return u.Marshal() }
func (u *Uint) UnmarshalAmino(bz []byte) error { return u.Unmarshal(bz) }
@ -235,7 +249,7 @@ func checkNewUint(i *big.Int) (Uint, error) {
if err := UintOverflow(i); err != nil {
return Uint{}, err
}
return Uint{i}, nil
return Uint{new(big.Int).Set(i)}, nil
}
// RelativePow raises x to the power of n, where x (and the result, z) are scaled by factor b
@ -247,7 +261,7 @@ func RelativePow(x, n, b Uint) (z Uint) {
return z
}
z = ZeroUint() // otherwise 0^a = 0
return
return z
}
z = x

View File

@ -69,6 +69,8 @@ func (s *uintTestSuite) TestUintPanics() {
s.Require().Panics(func() { uintmin.Sub(sdkmath.OneUint()) })
s.Require().Panics(func() { uintmin.Decr() })
s.Require().NotPanics(func() { sdkmath.Uint{}.BigInt() })
s.Require().Equal(uint64(0), sdkmath.MinUint(sdkmath.ZeroUint(), sdkmath.OneUint()).Uint64())
s.Require().Equal(uint64(1), sdkmath.MaxUint(sdkmath.ZeroUint(), sdkmath.OneUint()).Uint64())
@ -97,6 +99,26 @@ func (s *uintTestSuite) TestIsNil() {
s.Require().True(sdkmath.Uint{}.IsNil())
}
func (s *uintTestSuite) TestConvertToBigIntMutativeForUint() {
r := big.NewInt(30)
i := sdkmath.NewUintFromBigInt(r)
// Compare value of BigInt & BigIntMut
s.Require().Equal(i.BigInt(), i.BigIntMut())
// Modify BigIntMut() pointer and ensure i.BigIntMut() & i.BigInt() change
p1 := i.BigIntMut()
p1.SetInt64(40)
s.Require().Equal(big.NewInt(40), i.BigIntMut())
s.Require().Equal(big.NewInt(40), i.BigInt())
// Modify big.Int() pointer and ensure i.BigIntMut() & i.BigInt() don't change
p2 := i.BigInt()
p2.SetInt64(50)
s.Require().NotEqual(big.NewInt(50), i.BigIntMut())
s.Require().NotEqual(big.NewInt(50), i.BigInt())
}
func (s *uintTestSuite) TestArithUint() {
for d := 0; d < 1000; d++ {
n1 := uint64(rand.Uint32())
@ -222,7 +244,7 @@ func (s *uintTestSuite) TestSafeSub() {
}
for i, tc := range testCases {
tc := tc
if tc.panic {
s.Require().Panics(func() { tc.x.Sub(tc.y) })
continue
@ -261,6 +283,16 @@ func (s *uintTestSuite) TestParseUint() {
}
}
func (s *uintTestSuite) TestNewUintFromBigInt() {
r := big.NewInt(42)
i := sdkmath.NewUintFromBigInt(r)
s.Require().Equal(r, i.BigInt())
// modify r and ensure i doesn't change
r = r.SetInt64(100)
s.Require().NotEqual(r, i.BigInt())
}
func randuint() sdkmath.Uint {
return sdkmath.NewUint(rand.Uint64())
}
@ -311,7 +343,6 @@ func TestRoundTripMarshalToUint(t *testing.T) {
}
for _, value := range values {
value := value
t.Run(fmt.Sprintf("%d", value), func(t *testing.T) {
t.Parallel()