From 7db40eafe0d3c13b03c3f2c1cebf13a10b7ab59b Mon Sep 17 00:00:00 2001 From: Alex Vanin Date: Tue, 27 Oct 2020 13:25:16 +0300 Subject: [PATCH] [#122] Add precision converter Signed-off-by: Alex Vanin --- pkg/util/precision/converter.go | 93 ++++++++++++++++++++++++++++ pkg/util/precision/converter_test.go | 89 ++++++++++++++++++++++++++ 2 files changed, 182 insertions(+) create mode 100644 pkg/util/precision/converter.go create mode 100644 pkg/util/precision/converter_test.go diff --git a/pkg/util/precision/converter.go b/pkg/util/precision/converter.go new file mode 100644 index 000000000..3e6ea16d9 --- /dev/null +++ b/pkg/util/precision/converter.go @@ -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) +} diff --git a/pkg/util/precision/converter_test.go b/pkg/util/precision/converter_test.go new file mode 100644 index 000000000..3de662f2b --- /dev/null +++ b/pkg/util/precision/converter_test.go @@ -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)) + }) +}