forked from TrueCloudLab/frostfs-node
[#122] Add precision converter
Signed-off-by: Alex Vanin <alexey@nspcc.ru>
This commit is contained in:
parent
b93c146824
commit
7db40eafe0
2 changed files with 182 additions and 0 deletions
93
pkg/util/precision/converter.go
Normal file
93
pkg/util/precision/converter.go
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
package precision
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Converter is cached wrapper on `convert` function. It caches base and
|
||||||
|
// target precisions and factor.
|
||||||
|
converter struct {
|
||||||
|
base uint32 // base precision
|
||||||
|
target uint32 // target precision
|
||||||
|
|
||||||
|
factor *big.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fixed8Converter is a converter with base precision of Fixed8. It uses
|
||||||
|
// int64 values because there is a guarantee that balance contract will
|
||||||
|
// operate with `Deposit` and `Withdraw` amounts that less than 2**53-1.
|
||||||
|
// This is a JSON bound that uses neo node. Neo-go has int64 limit for
|
||||||
|
// `smartcontract.Parameter` of integer type.
|
||||||
|
Fixed8Converter struct {
|
||||||
|
converter
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const fixed8Precision = 8
|
||||||
|
|
||||||
|
// convert is the function that converts `n` to desired precision by using
|
||||||
|
// factor value.
|
||||||
|
func convert(n, factor *big.Int, decreasePrecision bool) *big.Int {
|
||||||
|
if decreasePrecision {
|
||||||
|
return new(big.Int).Div(n, factor)
|
||||||
|
}
|
||||||
|
|
||||||
|
return new(big.Int).Mul(n, factor)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConverter returns Fixed8Converter.
|
||||||
|
func NewConverter(precision uint32) Fixed8Converter {
|
||||||
|
var c Fixed8Converter
|
||||||
|
|
||||||
|
c.SetBalancePrecision(precision)
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c converter) toTarget(n *big.Int) *big.Int {
|
||||||
|
return convert(n, c.factor, c.base > c.target)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c converter) toBase(n *big.Int) *big.Int {
|
||||||
|
return convert(n, c.factor, c.base < c.target)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToFixed8 converts n of balance contract precision to Fixed8 precision.
|
||||||
|
func (c Fixed8Converter) ToFixed8(n int64) int64 {
|
||||||
|
return c.toBase(new(big.Int).SetInt64(n)).Int64()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToBalancePrecision converts n of Fixed8 precision to balance contract precision.
|
||||||
|
func (c Fixed8Converter) ToBalancePrecision(n int64) int64 {
|
||||||
|
return c.toTarget(new(big.Int).SetInt64(n)).Int64()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBalancePrecision prepares converter to work.
|
||||||
|
func (c *Fixed8Converter) SetBalancePrecision(precision uint32) {
|
||||||
|
exp := int(precision) - fixed8Precision
|
||||||
|
if exp < 0 {
|
||||||
|
exp = -exp
|
||||||
|
}
|
||||||
|
|
||||||
|
c.base = fixed8Precision
|
||||||
|
c.target = precision
|
||||||
|
c.factor = new(big.Int).SetInt64(int64(math.Pow10(exp)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert is a wrapper of convert function. Use cached `converter` struct
|
||||||
|
// if fromPrecision and toPrecision are constant.
|
||||||
|
func Convert(fromPrecision, toPrecision uint32, n *big.Int) *big.Int {
|
||||||
|
var decreasePrecision bool
|
||||||
|
|
||||||
|
exp := int(toPrecision) - int(fromPrecision)
|
||||||
|
if exp < 0 {
|
||||||
|
decreasePrecision = true
|
||||||
|
exp = -exp
|
||||||
|
}
|
||||||
|
|
||||||
|
factor := new(big.Int).SetInt64(int64(math.Pow10(exp)))
|
||||||
|
|
||||||
|
return convert(n, factor, decreasePrecision)
|
||||||
|
}
|
89
pkg/util/precision/converter_test.go
Normal file
89
pkg/util/precision/converter_test.go
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
package precision_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neofs-node/pkg/util/precision"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFixed8Converter_ToBalancePrecision(t *testing.T) {
|
||||||
|
var n int64 = 100*100_000_000 + 12_345_678
|
||||||
|
|
||||||
|
t.Run("same precision", func(t *testing.T) {
|
||||||
|
cnv := precision.NewConverter(8)
|
||||||
|
res := cnv.ToBalancePrecision(n)
|
||||||
|
require.Equal(t, n, res)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("bigger target", func(t *testing.T) {
|
||||||
|
cnv := precision.NewConverter(10)
|
||||||
|
exp := n * 100
|
||||||
|
res := cnv.ToBalancePrecision(n)
|
||||||
|
require.Equal(t, exp, res)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("less target", func(t *testing.T) {
|
||||||
|
cnv := precision.NewConverter(6)
|
||||||
|
exp := n / 100
|
||||||
|
res := cnv.ToBalancePrecision(n)
|
||||||
|
require.Equal(t, exp, res)
|
||||||
|
|
||||||
|
cnv = precision.NewConverter(0)
|
||||||
|
exp = n / 100_000_000
|
||||||
|
res = cnv.ToBalancePrecision(n)
|
||||||
|
require.Equal(t, exp, res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFixed8Converter_ToFixed8(t *testing.T) {
|
||||||
|
var n int64 = 100*10_000_000 + 12_345_678
|
||||||
|
|
||||||
|
t.Run("same precision", func(t *testing.T) {
|
||||||
|
cnv := precision.NewConverter(8)
|
||||||
|
res := cnv.ToFixed8(n)
|
||||||
|
require.Equal(t, n, res)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("bigger target", func(t *testing.T) {
|
||||||
|
cnv := precision.NewConverter(10)
|
||||||
|
exp := n / 100
|
||||||
|
res := cnv.ToFixed8(n)
|
||||||
|
require.Equal(t, exp, res)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("less target", func(t *testing.T) {
|
||||||
|
cnv := precision.NewConverter(6)
|
||||||
|
exp := n * 100
|
||||||
|
res := cnv.ToFixed8(n)
|
||||||
|
require.Equal(t, exp, res)
|
||||||
|
|
||||||
|
n = 1
|
||||||
|
cnv = precision.NewConverter(0)
|
||||||
|
exp = n * 100_000_000
|
||||||
|
res = cnv.ToFixed8(n)
|
||||||
|
require.Equal(t, exp, res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConvert(t *testing.T) {
|
||||||
|
n := big.NewInt(100*10_000_000 + 12_345_678)
|
||||||
|
|
||||||
|
t.Run("same precision", func(t *testing.T) {
|
||||||
|
require.Equal(t, n, precision.Convert(8, 8, n))
|
||||||
|
require.Equal(t, n, precision.Convert(0, 0, n))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("bigger target", func(t *testing.T) {
|
||||||
|
exp := new(big.Int).Mul(n, big.NewInt(100))
|
||||||
|
require.Equal(t, exp, precision.Convert(8, 10, n))
|
||||||
|
require.Equal(t, exp, precision.Convert(0, 2, n))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("less target", func(t *testing.T) {
|
||||||
|
exp := new(big.Int).Div(n, big.NewInt(100))
|
||||||
|
require.Equal(t, exp, precision.Convert(10, 8, n))
|
||||||
|
require.Equal(t, exp, precision.Convert(2, 0, n))
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in a new issue