diff --git a/x/gamm/keeper/math.go b/x/gamm/keeper/math.go index 1f0ccfc37c8..863abf371d6 100644 --- a/x/gamm/keeper/math.go +++ b/x/gamm/keeper/math.go @@ -10,6 +10,12 @@ import ( // TODO: Analyze choice here var powPrecision, _ = sdk.NewDecFromStr("0.00000001") +// Singletons +var zero sdk.Dec = sdk.ZeroDec() +var one_half sdk.Dec = sdk.MustNewDecFromStr("0.5") +var one sdk.Dec = sdk.OneDec() +var two sdk.Dec = sdk.MustNewDecFromStr("2") + // calcSpotPrice returns the spot price of the pool // This is the weight-adjusted balance of the tokens in the pool. // so spot_price = (B_in / W_in) / (B_out / W_out) @@ -77,7 +83,7 @@ func calcInGivenOut( diff := tokenBalanceOut.Sub(tokenAmountOut) y := tokenBalanceOut.Quo(diff) foo := pow(y, weightRatio) - foo = foo.Sub(sdk.OneDec()) + foo = foo.Sub(one) tokenAmountIn := sdk.OneDec().Sub(swapFee) return (tokenBalanceIn.Mul(foo)).Quo(tokenAmountIn) @@ -191,7 +197,8 @@ func calcPoolInGivenSingleOut( /*********************************************************/ -func subSign(a, b sdk.Dec) (sdk.Dec, bool) { +// absDifferenceWithSign returns | a - b |, (a - b).sign() +func absDifferenceWithSign(a, b sdk.Dec) (sdk.Dec, bool) { if a.GTE(b) { return a.Sub(b), false } else { @@ -208,18 +215,18 @@ func pow(base sdk.Dec, exp sdk.Dec) sdk.Dec { // You can see this by recalling that `i = (-1)^(.5)`. We have to go to complex numbers to define this. // (And would have to implement complex logarithms) // We don't have a need for negative bases, so we don't include any such logic. - if base.LTE(sdk.ZeroDec()) { - panic(fmt.Errorf("base have to be greater than zero")) + if !base.IsPositive() { + panic(fmt.Errorf("base must be greater than 0")) } - // TODO: Remove this, we can do a - if base.GTE(sdk.OneDec().MulInt64(2)) { - panic(fmt.Errorf("base have to be lesser than two")) + // TODO: Remove this if we want to generalize the function, + // we can adjust the algorithm in this setting. + if base.GTE(two) { + panic(fmt.Errorf("base must be lesser than two")) } // We will use an approximation algorithm to compute the power. // Since computing an integer power is easy, we split up the exponent into // an integer component and a fractional component. - // a integer := exp.TruncateDec() fractional := exp.Sub(integer) @@ -229,28 +236,38 @@ func pow(base sdk.Dec, exp sdk.Dec) sdk.Dec { return integerPow } - partialResult := powApprox(base, fractional, powPrecision) + fractionalPow := powApprox(base, fractional, powPrecision) - return integerPow.Mul(partialResult) + return integerPow.Mul(fractionalPow) } +// Contract: 0 < base < 2 func powApprox(base sdk.Dec, exp sdk.Dec, precision sdk.Dec) sdk.Dec { - if base.LTE(sdk.ZeroDec()) { - panic(fmt.Errorf("base must be greater than zero")) + if exp.IsZero() { + return sdk.ZeroDec() } - if base.GTE(sdk.OneDec().MulInt64(2)) { - panic(fmt.Errorf("base must be less than two")) + + // Common case optimization + // Optimize for it being equal to one-half + if exp.Equal(one_half) { + output, err := base.ApproxSqrt() + if err != nil { + panic(err) + } + return output } + // TODO: Make an approx-equal function, and then check if exp * 3 = 1, and do a check accordingly a := exp - x, xneg := subSign(base, sdk.OneDec()) + x, xneg := absDifferenceWithSign(base, one) term := sdk.OneDec() sum := sdk.OneDec() negative := false + // TODO: Document this computation via taylor expansion for i := 1; term.GTE(precision); i++ { bigK := sdk.OneDec().MulInt64(int64(i)) - c, cneg := subSign(a, bigK.Sub(sdk.OneDec())) + c, cneg := absDifferenceWithSign(a, bigK.Sub(one)) term = term.Mul(c.Mul(x)) term = term.Quo(bigK) diff --git a/x/gamm/keeper/math_test.go b/x/gamm/keeper/math_test.go index 3f34253766e..2edf8efca7a 100644 --- a/x/gamm/keeper/math_test.go +++ b/x/gamm/keeper/math_test.go @@ -8,13 +8,13 @@ import ( "github.com/stretchr/testify/require" ) -func TestSubSign(t *testing.T) { +func TestAbsDifferenceWithSign(t *testing.T) { decA, err := sdk.NewDecFromStr("3.2") require.NoError(t, err) decB, err := sdk.NewDecFromStr("4.3432389") require.NoError(t, err) - s, b := subSign(decA, decB) + s, b := absDifferenceWithSign(decA, decB) require.True(t, b) expectedDec, err := sdk.NewDecFromStr("1.1432389") @@ -272,57 +272,3 @@ func TestCalcPoolInGivenSingleOut(t *testing.T) { "expected value & actual value's difference should less than precision*10000", ) } - -func BenchmarkPow(b *testing.B) { - tests := []struct { - base sdk.Dec - exp sdk.Dec - }{ - { - base: sdk.NewDecWithPrec(12, 1), - exp: sdk.NewDecWithPrec(12, 1), - }, - { - base: sdk.NewDecWithPrec(5, 1), - exp: sdk.NewDecWithPrec(11122, 3), - }, - { - base: sdk.NewDecWithPrec(1, 1), - exp: sdk.NewDecWithPrec(492, 8), - }, - { - base: sdk.NewDecWithPrec(2423, 7), - exp: sdk.NewDecWithPrec(1213, 1), - }, - { - base: sdk.NewDecWithPrec(493, 3), - exp: sdk.NewDecWithPrec(121, 8), - }, - { - base: sdk.NewDecWithPrec(249, 6), - exp: sdk.NewDecWithPrec(2304, 1), - }, - { - base: sdk.NewDecWithPrec(2342, 4), - exp: sdk.NewDecWithPrec(322, 1), - }, - { - base: sdk.NewDecWithPrec(999, 6), - exp: sdk.NewDecWithPrec(1424, 1), - }, - { - base: sdk.NewDecWithPrec(1234, 3), - exp: sdk.NewDecWithPrec(1203, 1), - }, - { - base: sdk.NewDecWithPrec(122, 5), - exp: sdk.NewDecWithPrec(1232, 1), - }, - } - - for i := 0; i < b.N; i++ { - for _, test := range tests { - pow(test.base, test.exp) - } - } -} diff --git a/x/gamm/keeper/pow_bench_test.go b/x/gamm/keeper/pow_bench_test.go new file mode 100644 index 00000000000..0af73bb365c --- /dev/null +++ b/x/gamm/keeper/pow_bench_test.go @@ -0,0 +1,86 @@ +package keeper + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func BenchmarkPow(b *testing.B) { + tests := []struct { + base sdk.Dec + exp sdk.Dec + }{ + // TODO: Choose selection here more robustly + { + base: sdk.MustNewDecFromStr("1.2"), + exp: sdk.MustNewDecFromStr("1.2"), + }, + { + base: sdk.MustNewDecFromStr("0.5"), + exp: sdk.MustNewDecFromStr("11.122"), + }, + { + base: sdk.MustNewDecFromStr("0.1"), + exp: sdk.MustNewDecFromStr("0.00000492"), + }, + { + base: sdk.MustNewDecFromStr("0.0002423"), + exp: sdk.MustNewDecFromStr("0.1234"), + }, + { + base: sdk.MustNewDecFromStr("0.493"), + exp: sdk.MustNewDecFromStr("0.00000121"), + }, + { + base: sdk.MustNewDecFromStr("0.000249"), + exp: sdk.MustNewDecFromStr("2.304"), + }, + { + base: sdk.MustNewDecFromStr("0.2342"), + exp: sdk.MustNewDecFromStr("32.2"), + }, + { + base: sdk.MustNewDecFromStr("0.000999"), + exp: sdk.MustNewDecFromStr("142.4"), + }, + { + base: sdk.MustNewDecFromStr("1.234"), + exp: sdk.MustNewDecFromStr("120.3"), + }, + { + base: sdk.MustNewDecFromStr("0.00122"), + exp: sdk.MustNewDecFromStr("123.2"), + }, + } + + for i := 0; i < b.N; i++ { + for _, test := range tests { + pow(test.base, test.exp) + } + } +} + +func BenchmarkSqrtPow(b *testing.B) { + tests := []struct { + base sdk.Dec + }{ + // TODO: Choose selection here more robustly + { + base: sdk.MustNewDecFromStr("1.29847"), + }, + { + base: sdk.MustNewDecFromStr("1.313135"), + }, + { + base: sdk.MustNewDecFromStr("1.65976735939"), + }, + } + one_half := sdk.MustNewDecFromStr("0.5") + + for i := 0; i < b.N; i++ { + for _, test := range tests { + pow(test.base, one_half) + } + } +}