Skip to content

Commit

Permalink
feat(osmomath): Power and PowerMut functions with decimal exponent
Browse files Browse the repository at this point in the history
  • Loading branch information
p0mvn committed Dec 14, 2022
1 parent 79beaae commit 1cd4928
Show file tree
Hide file tree
Showing 2 changed files with 208 additions and 1 deletion.
42 changes: 42 additions & 0 deletions osmomath/decimal.go
Original file line number Diff line number Diff line change
Expand Up @@ -997,3 +997,45 @@ 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.
// N.B.: support for negative power can be added when needed.
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
}

// PowerMut 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. Mutates the receiver.
// N.B.: support for negative power can be added when needed.
func (d BigDec) PowerMut(power BigDec) BigDec {
d.i.Set(d.Power(power).i)
return d
}
167 changes: 166 additions & 1 deletion osmomath/decimal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,20 @@ import (

"github.com/osmosis-labs/osmosis/v13/app/apptesting/osmoassert"
"github.com/osmosis-labs/osmosis/v13/osmomath"
"github.com/osmosis-labs/osmosis/v13/osmoutils"
gammtypes "github.com/osmosis-labs/osmosis/v13/x/gamm/types"
)

type decimalTestSuite struct {
suite.Suite
}

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

func TestDecimalTestSuite(t *testing.T) {
suite.Run(t, new(decimalTestSuite))
}
Expand Down Expand Up @@ -1149,9 +1156,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 @@ -1225,7 +1241,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 @@ -1266,3 +1282,152 @@ 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 osmoutils.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: osmoutils.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: osmoutils.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: osmoutils.ErrTolerance{
AdditiveTolerance: minDecTolerance,
RoundingDir: osmomath.RoundUnconstrained, // TODO: understand if rounding behavior is acceptable.
},
},
"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: osmoutils.ErrTolerance{
AdditiveTolerance: minDecTolerance,
RoundingDir: osmomath.RoundUnconstrained, // TODO: understand if rounding behavior is acceptable.
},
},
"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: osmoutils.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))
})
})
}
}

// TestPower_Mutation tests that PowerMut mutates the receiver
// while Power is not.
func (s *decimalTestSuite) TestPower_Mutation() {

exponent := osmomath.NewBigDec(2)

tests := map[string]struct {
startValue osmomath.BigDec
expectedResult osmomath.BigDec
}{
"1": {
startValue: osmomath.OneDec(),
expectedResult: osmomath.OneDec(),
},
"0": {
startValue: osmomath.ZeroDec(),
expectedResult: osmomath.ZeroDec(),
},
"4": {
startValue: osmomath.MustNewDecFromStr("4.5"),
expectedResult: osmomath.MustNewDecFromStr("20.25"),
},
}

for name, tc := range tests {
s.Run(name, func() {

startMut := tc.startValue.Clone()
startNonMut := tc.startValue.Clone()

resultMut := startMut.PowerMut(exponent)
resultNonMut := startNonMut.Power(exponent)

s.assertMutResult(tc.expectedResult, tc.startValue, resultMut, resultNonMut, startMut, startNonMut)
})
}
}

0 comments on commit 1cd4928

Please sign in to comment.