mirror of
https://github.com/alexgo-io/gaze-brc20-indexer.git
synced 2026-01-12 14:34:54 +08:00
feat(decimals): add decimal utils
This commit is contained in:
1
go.mod
1
go.mod
@@ -45,6 +45,7 @@ require (
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/holiman/uint256 v1.2.4 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
|
||||
2
go.sum
2
go.sum
@@ -112,6 +112,8 @@ github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU=
|
||||
github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
|
||||
93
pkg/decimals/decimals.go
Normal file
93
pkg/decimals/decimals.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package decimals
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"reflect"
|
||||
|
||||
"github.com/Cleverse/go-utilities/utils"
|
||||
"github.com/gaze-network/indexer-network/pkg/logger"
|
||||
"github.com/gaze-network/indexer-network/pkg/logger/slogx"
|
||||
"github.com/gaze-network/uint128"
|
||||
"github.com/holiman/uint256"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultDivPrecision = 36
|
||||
)
|
||||
|
||||
func init() {
|
||||
decimal.DivisionPrecision = DefaultDivPrecision
|
||||
}
|
||||
|
||||
// MustFromString convert string to decimal.Decimal. Panic if error
|
||||
// string must be a valid number, not NaN, Inf or empty string.
|
||||
func MustFromString(s string) decimal.Decimal {
|
||||
return utils.Must(decimal.NewFromString(s))
|
||||
}
|
||||
|
||||
// ToDecimal convert any type to decimal.Decimal (safety floating point)
|
||||
func ToDecimal(ivalue any, decimals uint16) decimal.Decimal {
|
||||
value := new(big.Int)
|
||||
switch v := ivalue.(type) {
|
||||
case string:
|
||||
value.SetString(v, 10)
|
||||
case *big.Int:
|
||||
value = v
|
||||
case int64:
|
||||
value = big.NewInt(v)
|
||||
case int, int8, int16, int32:
|
||||
rValue := reflect.ValueOf(v)
|
||||
value.SetInt64(rValue.Int())
|
||||
case uint64:
|
||||
value = big.NewInt(0).SetUint64(v)
|
||||
case uint, uint8, uint16, uint32:
|
||||
rValue := reflect.ValueOf(v)
|
||||
value.SetUint64(rValue.Uint())
|
||||
case []byte:
|
||||
value.SetBytes(v)
|
||||
case uint128.Uint128:
|
||||
value = v.Big()
|
||||
case uint256.Int:
|
||||
value = v.ToBig()
|
||||
case *uint256.Int:
|
||||
value = v.ToBig()
|
||||
}
|
||||
return decimal.NewFromBigInt(value, -int32(decimals))
|
||||
}
|
||||
|
||||
// ToBigInt convert any type to *big.Int
|
||||
func ToBigInt(iamount any, decimals uint16) *big.Int {
|
||||
amount := decimal.NewFromFloat(0)
|
||||
switch v := iamount.(type) {
|
||||
case string:
|
||||
amount, _ = decimal.NewFromString(v)
|
||||
case float64:
|
||||
amount = decimal.NewFromFloat(v)
|
||||
case float32:
|
||||
amount = decimal.NewFromFloat32(v)
|
||||
case int64:
|
||||
amount = decimal.NewFromInt(v)
|
||||
case int, int8, int16, int32:
|
||||
rValue := reflect.ValueOf(v)
|
||||
amount = decimal.NewFromInt(rValue.Int())
|
||||
case decimal.Decimal:
|
||||
amount = v
|
||||
case *decimal.Decimal:
|
||||
amount = *v
|
||||
case big.Float:
|
||||
amount, _ = decimal.NewFromString(v.String())
|
||||
case *big.Float:
|
||||
amount, _ = decimal.NewFromString(v.String())
|
||||
}
|
||||
return amount.Mul(PowerOfTen(decimals)).BigInt()
|
||||
}
|
||||
|
||||
// ToUint256 convert any type to *uint256.Int
|
||||
func ToUint256(iamount any, decimals uint16) *uint256.Int {
|
||||
result := new(uint256.Int)
|
||||
if overflow := result.SetFromBig(ToBigInt(iamount, decimals)); overflow {
|
||||
logger.Panic("ToUint256 overflow", slogx.Any("amount", iamount), slogx.Uint16("decimals", decimals))
|
||||
}
|
||||
return result
|
||||
}
|
||||
80
pkg/decimals/decimals_test.go
Normal file
80
pkg/decimals/decimals_test.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package decimals
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/gaze-network/uint128"
|
||||
"github.com/holiman/uint256"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestToDecimal(t *testing.T) {
|
||||
t.Run("check_supported_types", func(t *testing.T) {
|
||||
testcases := []struct {
|
||||
decimals uint16
|
||||
value uint64
|
||||
expected string
|
||||
}{
|
||||
{0, 1, "1"},
|
||||
{1, 1, "0.1"},
|
||||
{2, 1, "0.01"},
|
||||
{3, 1, "0.001"},
|
||||
{18, 1, "0.000000000000000001"},
|
||||
{36, 1, "0.000000000000000000000000000000000001"},
|
||||
}
|
||||
typesConv := []func(uint64) any{
|
||||
func(i uint64) any { return int(i) },
|
||||
func(i uint64) any { return int8(i) },
|
||||
func(i uint64) any { return int16(i) },
|
||||
func(i uint64) any { return int32(i) },
|
||||
func(i uint64) any { return int64(i) },
|
||||
func(i uint64) any { return uint(i) },
|
||||
func(i uint64) any { return uint8(i) },
|
||||
func(i uint64) any { return uint16(i) },
|
||||
func(i uint64) any { return uint32(i) },
|
||||
func(i uint64) any { return uint64(i) },
|
||||
func(i uint64) any { return fmt.Sprint(i) },
|
||||
func(i uint64) any { return new(big.Int).SetUint64(i) },
|
||||
func(i uint64) any { return new(uint128.Uint128).Add64(i) },
|
||||
func(i uint64) any { return uint256.NewInt(i) },
|
||||
}
|
||||
for _, tc := range testcases {
|
||||
t.Run(fmt.Sprintf("%d_%d", tc.decimals, tc.value), func(t *testing.T) {
|
||||
for _, conv := range typesConv {
|
||||
input := conv(tc.value)
|
||||
t.Run(fmt.Sprintf("%T", input), func(t *testing.T) {
|
||||
actual := ToDecimal(input, tc.decimals)
|
||||
assert.Equal(t, tc.expected, actual.String())
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
testcases := []struct {
|
||||
decimals uint16
|
||||
value interface{}
|
||||
expected string
|
||||
}{
|
||||
{0, uint64(math.MaxUint64), "18446744073709551615"},
|
||||
{18, uint64(math.MaxUint64), "18.446744073709551615"},
|
||||
{36, uint64(math.MaxUint64), "0.000000000000000018446744073709551615"},
|
||||
/* max uint128 */
|
||||
{0, uint128.Max, "340282366920938463463374607431768211455"},
|
||||
{18, uint128.Max, "340282366920938463463.374607431768211455"},
|
||||
{36, uint128.Max, "340.282366920938463463374607431768211455"},
|
||||
/* max uint256 */
|
||||
{0, new(uint256.Int).SetAllOne(), "115792089237316195423570985008687907853269984665640564039457584007913129639935"},
|
||||
{18, new(uint256.Int).SetAllOne(), "115792089237316195423570985008687907853269984665640564039457.584007913129639935"},
|
||||
{36, new(uint256.Int).SetAllOne(), "115792089237316195423570985008687907853269.984665640564039457584007913129639935"},
|
||||
}
|
||||
for _, tc := range testcases {
|
||||
t.Run(fmt.Sprintf("%d_%s", tc.decimals, tc.value), func(t *testing.T) {
|
||||
actual := ToDecimal(tc.value, tc.decimals)
|
||||
assert.Equal(t, tc.expected, actual.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
97
pkg/decimals/power_of_ten.go
Normal file
97
pkg/decimals/power_of_ten.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package decimals
|
||||
|
||||
import (
|
||||
"github.com/shopspring/decimal"
|
||||
"golang.org/x/exp/constraints"
|
||||
)
|
||||
|
||||
// max precision is 36
|
||||
const (
|
||||
minPowerOfTen = -DefaultDivPrecision
|
||||
maxPowerOfTen = DefaultDivPrecision
|
||||
)
|
||||
|
||||
var powerOfTen = map[int64]decimal.Decimal{
|
||||
minPowerOfTen: MustFromString("0.000000000000000000000000000000000001"),
|
||||
-35: MustFromString("0.00000000000000000000000000000000001"),
|
||||
-34: MustFromString("0.0000000000000000000000000000000001"),
|
||||
-33: MustFromString("0.000000000000000000000000000000001"),
|
||||
-32: MustFromString("0.00000000000000000000000000000001"),
|
||||
-31: MustFromString("0.0000000000000000000000000000001"),
|
||||
-30: MustFromString("0.000000000000000000000000000001"),
|
||||
-29: MustFromString("0.00000000000000000000000000001"),
|
||||
-28: MustFromString("0.0000000000000000000000000001"),
|
||||
-27: MustFromString("0.000000000000000000000000001"),
|
||||
-26: MustFromString("0.00000000000000000000000001"),
|
||||
-25: MustFromString("0.0000000000000000000000001"),
|
||||
-24: MustFromString("0.000000000000000000000001"),
|
||||
-23: MustFromString("0.00000000000000000000001"),
|
||||
-22: MustFromString("0.0000000000000000000001"),
|
||||
-21: MustFromString("0.000000000000000000001"),
|
||||
-20: MustFromString("0.00000000000000000001"),
|
||||
-19: MustFromString("0.0000000000000000001"),
|
||||
-18: MustFromString("0.000000000000000001"),
|
||||
-17: MustFromString("0.00000000000000001"),
|
||||
-16: MustFromString("0.0000000000000001"),
|
||||
-15: MustFromString("0.000000000000001"),
|
||||
-14: MustFromString("0.00000000000001"),
|
||||
-13: MustFromString("0.0000000000001"),
|
||||
-12: MustFromString("0.000000000001"),
|
||||
-11: MustFromString("0.00000000001"),
|
||||
-10: MustFromString("0.0000000001"),
|
||||
-9: MustFromString("0.000000001"),
|
||||
-8: MustFromString("0.00000001"),
|
||||
-7: MustFromString("0.0000001"),
|
||||
-6: MustFromString("0.000001"),
|
||||
-5: MustFromString("0.00001"),
|
||||
-4: MustFromString("0.0001"),
|
||||
-3: MustFromString("0.001"),
|
||||
-2: MustFromString("0.01"),
|
||||
-1: MustFromString("0.1"),
|
||||
0: MustFromString("1"),
|
||||
1: MustFromString("10"),
|
||||
2: MustFromString("100"),
|
||||
3: MustFromString("1000"),
|
||||
4: MustFromString("10000"),
|
||||
5: MustFromString("100000"),
|
||||
6: MustFromString("1000000"),
|
||||
7: MustFromString("10000000"),
|
||||
8: MustFromString("100000000"),
|
||||
9: MustFromString("1000000000"),
|
||||
10: MustFromString("10000000000"),
|
||||
11: MustFromString("100000000000"),
|
||||
12: MustFromString("1000000000000"),
|
||||
13: MustFromString("10000000000000"),
|
||||
14: MustFromString("100000000000000"),
|
||||
15: MustFromString("1000000000000000"),
|
||||
16: MustFromString("10000000000000000"),
|
||||
17: MustFromString("100000000000000000"),
|
||||
18: MustFromString("1000000000000000000"),
|
||||
19: MustFromString("10000000000000000000"),
|
||||
20: MustFromString("100000000000000000000"),
|
||||
21: MustFromString("1000000000000000000000"),
|
||||
22: MustFromString("10000000000000000000000"),
|
||||
23: MustFromString("100000000000000000000000"),
|
||||
24: MustFromString("1000000000000000000000000"),
|
||||
25: MustFromString("10000000000000000000000000"),
|
||||
26: MustFromString("100000000000000000000000000"),
|
||||
27: MustFromString("1000000000000000000000000000"),
|
||||
28: MustFromString("10000000000000000000000000000"),
|
||||
29: MustFromString("100000000000000000000000000000"),
|
||||
30: MustFromString("1000000000000000000000000000000"),
|
||||
31: MustFromString("10000000000000000000000000000000"),
|
||||
32: MustFromString("100000000000000000000000000000000"),
|
||||
33: MustFromString("1000000000000000000000000000000000"),
|
||||
34: MustFromString("10000000000000000000000000000000000"),
|
||||
35: MustFromString("100000000000000000000000000000000000"),
|
||||
maxPowerOfTen: MustFromString("1000000000000000000000000000000000000"),
|
||||
}
|
||||
|
||||
// PowerOfTen optimized arithmetic performance for 10^n.
|
||||
func PowerOfTen[T constraints.Integer](n T) decimal.Decimal {
|
||||
nInt64 := int64(n)
|
||||
if val, ok := powerOfTen[nInt64]; ok {
|
||||
return val
|
||||
}
|
||||
return powerOfTen[1].Pow(decimal.NewFromInt(nInt64))
|
||||
}
|
||||
44
pkg/decimals/power_of_ten_test.go
Normal file
44
pkg/decimals/power_of_ten_test.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package decimals
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPowerOfTen(t *testing.T) {
|
||||
for n := int64(-36); n <= 36; n++ {
|
||||
t.Run(fmt.Sprint(n), func(t *testing.T) {
|
||||
expected := powerOfTenString(n)
|
||||
actual := PowerOfTen(n)
|
||||
assert.Equal(t, expected, actual.String())
|
||||
})
|
||||
}
|
||||
t.Run("constants", func(t *testing.T) {
|
||||
for n, p := range powerOfTen {
|
||||
t.Run(p.String(), func(t *testing.T) {
|
||||
require.False(t, p.IsZero(), "power of ten must not be zero")
|
||||
actual := PowerOfTen(n)
|
||||
assert.Equal(t, p, actual)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// powerOfTenString add zero padding to power of ten string
|
||||
func powerOfTenString(n int64) string {
|
||||
s := "1"
|
||||
if n < 0 {
|
||||
for i := int64(0); i < -n-1; i++ {
|
||||
s = "0" + s
|
||||
}
|
||||
s = "0." + s
|
||||
} else {
|
||||
for i := int64(0); i < n; i++ {
|
||||
s = s + "0"
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
Reference in New Issue
Block a user