feat(math): Upstream GDA based decimal type (#21982)

Co-authored-by: samricotta <samanthalricotta@gmail.com>
Co-authored-by: samricotta <37125168+samricotta@users.noreply.github.com>
This commit is contained in:
Alexander Peters 2024-11-22 11:29:01 +01:00 committed by GitHub
parent 685218e532
commit 4ed1087cf5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 5408 additions and 2233 deletions

View File

@ -0,0 +1,80 @@
---
sidebar_position: 1
---
# Decimal Handling in Cosmos SDK
## Introduction
In the Cosmos SDK, there are two types of decimals: `LegacyDec` and `Dec`. `LegacyDec` is the older decimal type that is still available for use, while `Dec` is the newer, more performant decimal type. The implementation of `Dec` is adapted from Regen Network's `regen-ledger`, specifically from [this module](https://github.com/regen-network/regen-ledger/tree/main/types/math). Migrating from `LegacyDec` to `Dec` involves state-breaking changes, specifically:
* **Data Format**: The internal representation of decimals changes, affecting how data is stored and processed.
* **Precision Handling**: `Dec` supports flexible precision up to 34 decimal places, unlike `LegacyDec` which has a fixed precision of 18 decimal places.
These changes require a state migration to update existing decimal values to the new format. It is recommended to use `Dec` for new modules to leverage its enhanced performance and flexibility.
## Why the Change?
* Historically we have wrapped a `big.Int` to represent decimals in the Cosmos SDK and never had a decimal type. Finally, we have a decimal type that is more efficient and accurate.
* `Dec` uses the [apd](https://github.com/cockroachdb/apd) library for arbitrary precision decimals, suitable for accurate financial calculations.
* `Dec` operations are safer for concurrent use as they do not mutate the original values.
* `Dec` operations are faster and more efficient than `LegacyDec`.
## Using `Dec` in Modules that haven't used `LegacyDec`
If you are creating a new module or updating an existing module that has not used `LegacyDec`, you can directly use `Dec`.
Ensure proper error handling.
```
-- math.NewLegacyDecFromInt64(100)
++ math.NewDecFromInt64(100)
-- math.LegacyNewDecWithPrec(100, 18)
++ math.NewDecWithPrec(100, 18)
-- math.LegacyNewDecFromStr("100")
++ math.NewDecFromString("100")
-- math.LegacyNewDecFromStr("100.000000000000000000").Quo(math.LegacyNewDecFromInt(2))
++ foo, err := math.NewDecFromString("100.000000000000000000")
++ foo.Quo(math.NewDecFromInt(2))
-- math.LegacyNewDecFromStr("100.000000000000000000").Add(math.LegacyNewDecFromInt(2))
++ foo, err := math.NewDecFromString("100.000000000000000000")
++ foo.Add(math.NewDecFromInt(2))
-- math.LegacyNewDecFromStr("100.000000000000000000").Sub(math.LegacyNewDecFromInt(2))
++ foo, err := math.NewDecFromString("100.000000000000000000")
++ foo.Sub(math.NewDecFromInt(2))
```
## Modules migrating from `LegacyDec` to `Dec`
When migrating from `LegacyDec` to `Dec`, you need to update your module to use the new decimal type. **These types are state breaking changes and require a migration.**
## Precision Handling
The reason for the state breaking change is the difference in precision handling between the two decimal types:
* **LegacyDec**: Fixed precision of 18 decimal places.
* **Dec**: Flexible precision up to 34 decimal places using the apd library.
## Impact of Precision Change
The increase in precision from 18 to 34 decimal places allows for more detailed decimal values but requires data migration. This change in how data is formatted and stored is a key aspect of why the transition is considered state-breaking.
## Converting `LegacyDec` to `Dec` without storing the data
If you would like to convert a `LegacyDec` to a `Dec` without a state migration changing how the data is handled internally within the application logic and not how it's stored or represented. You can use the following methods.
```go
func LegacyDecToDec(ld LegacyDec) (Dec, error) {
return NewDecFromString(ld.String())
}
```
```go
func DecToLegacyDec(ld Dec) (LegacyDec, error) {
return LegacyDecFromString(ld.String())
}
```

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
// 0.00000000000000000000000000000000000
// 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

@ -3,17 +3,29 @@ module cosmossdk.io/math
go 1.20
require (
github.com/cockroachdb/apd/v3 v3.2.1
github.com/stretchr/testify v1.9.0
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0
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
github.com/pkg/errors v0.9.1 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
golang.org/x/net v0.28.0 // indirect
golang.org/x/sys v0.24.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240709173604-40e1e62336c5 // indirect
google.golang.org/grpc v1.64.1 // indirect
google.golang.org/protobuf v1.34.2 // 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.

View File

@ -1,28 +1,45 @@
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/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
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/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
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/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/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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o=
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 h1:985EYyeCOxTpcgOTJpflJUwOeEz0CQOdPt73OzpE9F8=
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240709173604-40e1e62336c5 h1:SbSDUWW1PAO24TNpLdeheoYPd7kllICcLU52x6eD4kQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240709173604-40e1e62336c5/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA=
google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
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.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
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

@ -499,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()
@ -548,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 {

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
}

1329
math/legacy_dec_test.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -343,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()

View File

@ -82,7 +82,7 @@ require (
github.com/chzyer/readline v1.5.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/cockroachdb/apd/v2 v2.0.2 // indirect
github.com/cockroachdb/apd/v3 v3.2.1 // indirect
github.com/cockroachdb/errors v1.11.3 // indirect
github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce // indirect
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect

View File

@ -297,8 +297,8 @@ github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWH
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b8034E=
github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw=
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/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4=
github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU=
github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I=

View File

@ -85,7 +85,7 @@ require (
github.com/chzyer/readline v1.5.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/cockroachdb/apd/v2 v2.0.2 // indirect
github.com/cockroachdb/apd/v3 v3.2.1 // indirect
github.com/cockroachdb/errors v1.11.3 // indirect
github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce // indirect
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect

View File

@ -296,8 +296,8 @@ github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWH
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b8034E=
github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw=
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/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4=
github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU=
github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I=

View File

@ -95,7 +95,7 @@ require (
github.com/chzyer/readline v1.5.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/cockroachdb/apd/v2 v2.0.2 // indirect
github.com/cockroachdb/apd/v3 v3.2.1 // indirect
github.com/cockroachdb/errors v1.11.3 // indirect
github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce // indirect
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect

View File

@ -295,8 +295,8 @@ github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWH
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b8034E=
github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw=
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/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4=
github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU=
github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I=

View File

@ -18,7 +18,7 @@ require (
cosmossdk.io/x/gov v0.0.0-20230925135524-a1bc045b3190
cosmossdk.io/x/mint v0.0.0-00010101000000-000000000000
cosmossdk.io/x/staking v0.0.0-00010101000000-000000000000
github.com/cockroachdb/apd/v2 v2.0.2
github.com/cockroachdb/apd/v3 v3.2.1
github.com/cosmos/cosmos-proto v1.0.0-beta.5
github.com/cosmos/cosmos-sdk v0.53.0
github.com/cosmos/gogoproto v1.7.0

View File

@ -94,8 +94,8 @@ github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWH
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b8034E=
github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw=
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/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4=
github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU=
github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I=

View File

@ -2,12 +2,12 @@
package math
import (
"errors"
"fmt"
"github.com/cockroachdb/apd/v2"
"github.com/cockroachdb/apd/v3"
errorsmod "cosmossdk.io/errors"
grouperrors "cosmossdk.io/x/group/errors"
"cosmossdk.io/x/group/errors"
)
// Dec is a wrapper struct around apd.Decimal that does no mutation of apd.Decimal's when performing
@ -23,10 +23,10 @@ type Dec struct {
func NewPositiveDecFromString(s string) (Dec, error) {
d, err := NewDecFromString(s)
if err != nil {
return Dec{}, grouperrors.ErrInvalidDecString.Wrap(err.Error())
return Dec{}, errors.ErrInvalidDecString.Wrap(err.Error())
}
if !d.IsPositive() {
return Dec{}, grouperrors.ErrInvalidDecString.Wrapf("expected a positive decimal, got %s", s)
return Dec{}, errors.ErrInvalidDecString.Wrapf("expected a positive decimal, got %s", s)
}
return d, nil
}
@ -34,10 +34,10 @@ func NewPositiveDecFromString(s string) (Dec, error) {
func NewNonNegativeDecFromString(s string) (Dec, error) {
d, err := NewDecFromString(s)
if err != nil {
return Dec{}, grouperrors.ErrInvalidDecString.Wrap(err.Error())
return Dec{}, errors.ErrInvalidDecString.Wrap(err.Error())
}
if d.IsNegative() {
return Dec{}, grouperrors.ErrInvalidDecString.Wrapf("expected a non-negative decimal, got %s", s)
return Dec{}, errors.ErrInvalidDecString.Wrapf("expected a non-negative decimal, got %s", s)
}
return d, nil
}
@ -51,11 +51,11 @@ func (x Dec) IsPositive() bool {
func NewDecFromString(s string) (Dec, error) {
d, _, err := apd.NewFromString(s)
if err != nil {
return Dec{}, grouperrors.ErrInvalidDecString.Wrap(err.Error())
return Dec{}, errors.ErrInvalidDecString.Wrap(err.Error())
}
if d.Form != apd.Finite {
return Dec{}, grouperrors.ErrInvalidDecString.Wrapf("expected a finite decimal, got %s", s)
return Dec{}, errors.ErrInvalidDecString.Wrapf("expected a finite decimal, got %s", s)
}
return Dec{*d}, nil
@ -136,7 +136,7 @@ func SubNonNegative(x, y Dec) (Dec, error) {
}
if z.IsNegative() {
return z, errors.New("result negative during non-negative subtraction")
return z, fmt.Errorf("result negative during non-negative subtraction")
}
return z, nil