Skip to content

Commit

Permalink
feat(osmomath): Power with decimal exponent (#3731)
Browse files Browse the repository at this point in the history
* feat(osmomath): exp2 function

* export exp2

* changelog

* refactor ErrTolerance to use Dec instead of Int for additive tolerance

* Update osmomath/exp2.go

* Update osmomath/exp2.go

* Update osmomath/exp2.go

* Update osmomath/exp2_test.go

* Update osmomath/exp2_test.go

* feat(osmomath): Power and PowerMut with decimal exponent

* changelog

* merge conflict

* clean up

* remove `PowerMut`

* update spec about error bound

* Update CHANGELOG.md

* Update CHANGELOG.md

Co-authored-by: Dev Ojha <[email protected]>

Co-authored-by: Dev Ojha <[email protected]>
  • Loading branch information
p0mvn and ValarDragon authored Dec 21, 2022
1 parent d1349db commit c52d5b2
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* [#3678](https://github.com/osmosis-labs/osmosis/pull/3678) implement mutative `PowerIntegerMut` function on `osmomath.BigDec`.
* [#3708](https://github.com/osmosis-labs/osmosis/pull/3708) `Exp2` function to compute 2^decimal.
* [#3693](https://github.com/osmosis-labs/osmosis/pull/3693) Add `EstimateSwapExactAmountOut` query to stargate whitelist
* [#3731](https://github.com/osmosis-labs/osmosis/pull/3731) BigDec Power functions with decimal exponent.

### API breaks

Expand Down
36 changes: 36 additions & 0 deletions osmomath/decimal.go
Original file line number Diff line number Diff line change
Expand Up @@ -997,3 +997,39 @@ func (d BigDec) PowerIntegerMut(power uint64) BigDec {

return d.MulMut(tmp)
}

// Power returns a result of raising the given big dec to
// a positive decimal power. Panics if the power is negative.
// Panics if the base is negative. Does not mutate the receiver.
// The max supported exponent is defined by the global maxSupportedExponent.
// If a greater exponent is given, the function panics.
// The error is not bounded but expected to be around 10^-18, use with care.
// See the underlying Exp2, LogBase2 and Mul for the details of their bounds.
func (d BigDec) Power(power BigDec) BigDec {
if d.IsNegative() {
panic(fmt.Sprintf("negative base is not supported for Power(), base was (%s)", d))
}
if power.IsNegative() {
panic(fmt.Sprintf("negative power is not supported for Power(), power was (%s)", power))
}
if power.Abs().GT(maxSupportedExponent) {
panic(fmt.Sprintf("integer exponent %s is too large, max (%s)", power, maxSupportedExponent))
}
if power.IsInteger() {
return d.PowerInteger(power.TruncateInt().Uint64())
}
if power.IsZero() {
return OneDec()
}
if d.IsZero() {
return ZeroDec()
}
if d.Equal(twoBigDec) {
return Exp2(power)
}

// d^power = exp2(power * log_2{base})
result := Exp2(d.LogBase2().Mul(power))

return result
}
129 changes: 128 additions & 1 deletion osmomath/decimal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/stretchr/testify/suite"
"gopkg.in/yaml.v2"

"github.com/osmosis-labs/osmosis/v13/app/apptesting/osmoassert"
"github.com/osmosis-labs/osmosis/v13/osmomath"
gammtypes "github.com/osmosis-labs/osmosis/v13/x/gamm/types"
)
Expand All @@ -20,6 +21,12 @@ type decimalTestSuite struct {
suite.Suite
}

var (
zeroAdditiveErrTolerance = osmomath.ErrTolerance{
AdditiveTolerance: sdk.ZeroDec(),
}
)

func TestDecimalTestSuite(t *testing.T) {
suite.Run(t, new(decimalTestSuite))
}
Expand Down Expand Up @@ -1144,9 +1151,18 @@ func (s *decimalTestSuite) TestPowerInteger() {
tolerance = tc.expectedToleranceOverwrite
}

// Main system under test
actualResult := tc.base.PowerInteger(tc.exponent)
require.True(osmomath.DecApproxEq(s.T(), tc.expectedResult, actualResult, tolerance))

// Secondary system under test.
// To reduce boilerplate from the same test cases when exponent is a
// positive integer, we also test Power().
// Negative exponent and base are not supported for Power()
if tc.exponent >= 0 && !tc.base.IsNegative() {
actualResult2 := tc.base.Power(osmomath.NewDecFromInt(osmomath.NewIntFromUint64(tc.exponent)))
require.True(osmomath.DecApproxEq(s.T(), tc.expectedResult, actualResult2, tolerance))
}
})
}
}
Expand Down Expand Up @@ -1219,7 +1235,7 @@ func (s *decimalTestSuite) TestMul_Mutation() {
}
}

// TestMul_Mutation tests that PowerIntegerMut mutates the receiver
// TestPowerInteger_Mutation tests that PowerIntegerMut mutates the receiver
// while PowerInteger is not.
func (s *decimalTestSuite) TestPowerInteger_Mutation() {

Expand Down Expand Up @@ -1260,3 +1276,114 @@ func (s *decimalTestSuite) TestPowerInteger_Mutation() {
})
}
}

func (s *decimalTestSuite) TestPower() {
tests := map[string]struct {
base osmomath.BigDec
exponent osmomath.BigDec
expectedResult osmomath.BigDec
expectPanic bool
errTolerance osmomath.ErrTolerance
}{
// N.B.: integer exponents are tested under TestPowerInteger.

"3 ^ 2 = 9 (integer base and integer exponent)": {
base: osmomath.NewBigDec(3),
exponent: osmomath.NewBigDec(2),

expectedResult: osmomath.NewBigDec(9),

errTolerance: zeroAdditiveErrTolerance,
},
"2^0.5 (base of 2 and non-integer exponent)": {
base: osmomath.MustNewDecFromStr("2"),
exponent: osmomath.MustNewDecFromStr("0.5"),

// https://www.wolframalpha.com/input?i=2%5E0.5+37+digits
expectedResult: osmomath.MustNewDecFromStr("1.414213562373095048801688724209698079"),

errTolerance: osmomath.ErrTolerance{
AdditiveTolerance: minDecTolerance,
RoundingDir: osmomath.RoundDown,
},
},
"3^0.33 (integer base other than 2 and non-integer exponent)": {
base: osmomath.MustNewDecFromStr("3"),
exponent: osmomath.MustNewDecFromStr("0.33"),

// https://www.wolframalpha.com/input?i=3%5E0.33+37+digits
expectedResult: osmomath.MustNewDecFromStr("1.436977652184851654252692986409357265"),

errTolerance: osmomath.ErrTolerance{
AdditiveTolerance: minDecTolerance,
RoundingDir: osmomath.RoundDown,
},
},
"e^0.98999 (non-integer base and non-integer exponent)": {
base: osmomath.EulersNumber,
exponent: osmomath.MustNewDecFromStr("0.9899"),

// https://www.wolframalpha.com/input?i=e%5E0.9899+37+digits
expectedResult: osmomath.MustNewDecFromStr("2.690965362357751196751808686902156603"),

errTolerance: osmomath.ErrTolerance{
AdditiveTolerance: minDecTolerance,
RoundingDir: osmomath.RoundUnconstrained,
},
},
"10^0.001 (small non-integer exponent)": {
base: osmomath.NewBigDec(10),
exponent: osmomath.MustNewDecFromStr("0.001"),

// https://www.wolframalpha.com/input?i=10%5E0.001+37+digits
expectedResult: osmomath.MustNewDecFromStr("1.002305238077899671915404889328110554"),

errTolerance: osmomath.ErrTolerance{
AdditiveTolerance: minDecTolerance,
RoundingDir: osmomath.RoundUnconstrained,
},
},
"13^100.7777 (large non-integer exponent)": {
base: osmomath.NewBigDec(13),
exponent: osmomath.MustNewDecFromStr("100.7777"),

// https://www.wolframalpha.com/input?i=13%5E100.7777+37+digits
expectedResult: osmomath.MustNewDecFromStr("1.822422110233759706998600329118969132").Mul(osmomath.NewBigDec(10).PowerInteger(112)),

errTolerance: osmomath.ErrTolerance{
MultiplicativeTolerance: minDecTolerance,
RoundingDir: osmomath.RoundDown,
},
},
"large non-integer exponent with large non-integer base - panics": {
base: osmomath.MustNewDecFromStr("169.137"),
exponent: osmomath.MustNewDecFromStr("100.7777"),

expectPanic: true,
},
"negative base - panic": {
base: osmomath.NewBigDec(-3),
exponent: osmomath.MustNewDecFromStr("4"),

expectPanic: true,
},
"negative exponent - panic": {
base: osmomath.NewBigDec(1),
exponent: osmomath.MustNewDecFromStr("-4"),

expectPanic: true,
},
}

for name, tc := range tests {
tc := tc
s.Run(name, func() {
osmoassert.ConditionalPanic(s.T(), tc.expectPanic, func() {

actualResult := tc.base.Power(tc.exponent)

s.Require().Equal(0, tc.errTolerance.CompareBigDec(tc.expectedResult, actualResult))
})
})
}
}

0 comments on commit c52d5b2

Please sign in to comment.