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:
parent
685218e532
commit
4ed1087cf5
80
docs/build/building-modules/18-decimal-handling.md
vendored
Normal file
80
docs/build/building-modules/18-decimal-handling.md
vendored
Normal 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())
|
||||
}
|
||||
```
|
||||
|
||||
1305
math/dec.go
1305
math/dec.go
File diff suppressed because it is too large
Load Diff
353
math/dec_bench_test.go
Normal file
353
math/dec_bench_test.go
Normal 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
323
math/dec_examples_test.go
Normal 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
527
math/dec_rapid_test.go
Normal 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)
|
||||
}
|
||||
2645
math/dec_test.go
2645
math/dec_test.go
File diff suppressed because it is too large
Load Diff
20
math/go.mod
20
math/go.mod
@ -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.
|
||||
|
||||
41
math/go.sum
41
math/go.sum
@ -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=
|
||||
|
||||
@ -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
971
math/legacy_dec.go
Normal 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
1329
math/legacy_dec_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -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()
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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=
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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=
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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=
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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=
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user