Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(osmomath): Exp2 function #3708

Merged
merged 20 commits into from
Dec 16, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* [#3677](https://github.com/osmosis-labs/osmosis/pull/3677) Add methods for cloning and mutative multiplication on osmomath.BigDec.
* [#3676](https://github.com/osmosis-labs/osmosis/pull/3676) implement `PowerInteger` function on `osmomath.BigDec`
* [#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

### Bug fixes
Expand Down
93 changes: 93 additions & 0 deletions osmomath/exp2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package osmomath

import "fmt"

var (
// Truncated at precision end.
// See scripts/approximations/main.py exponent_approximation_choice function for details.
numeratorCoefficients13Param = []BigDec{
MustNewDecFromStr("1.000000000000000000000044212244679434"),
MustNewDecFromStr("0.352032455817400196452603772766844426"),
MustNewDecFromStr("0.056507868883666405413116800969512484"),
MustNewDecFromStr("0.005343900728213034434757419480319916"),
MustNewDecFromStr("0.000317708814342353603087543715930732"),
MustNewDecFromStr("0.000011429747507407623028722262874632"),
MustNewDecFromStr("0.000000198381965651614980168744540366"),
}

// Rounded up at precision end.
// See scripts/approximations/main.py exponent_approximation_choice function for details.
denominatorCoefficients13Param = []BigDec{
OneDec(),
MustNewDecFromStr("0.341114724742545112949699755780593311").Neg(),
MustNewDecFromStr("0.052724071627342653404436933178482287"),
MustNewDecFromStr("0.004760950735524957576233524801866342").Neg(),
MustNewDecFromStr("0.000267168475410566529819971616894193"),
MustNewDecFromStr("0.000008923715368802211181557353097439").Neg(),
MustNewDecFromStr("0.000000140277233177373698516010555916"),
}
p0mvn marked this conversation as resolved.
Show resolved Hide resolved

// maxSupportedExponent = 2^10. The value is chosen by finding
// at which value some of the underlying internal functions overflow.
// If needed, exp2 can be reimplemented to allow for greater exponents.
p0mvn marked this conversation as resolved.
Show resolved Hide resolved
maxSupportedExponent = MustNewDecFromStr("2").PowerInteger(9)
)

// Exp2 takes 2 to the power of a given non-negative decimal exponent.
p0mvn marked this conversation as resolved.
Show resolved Hide resolved
// and returns the result.
// The computation is performed by using th following property:
// 2^decimal_exp = 2^{integer_exp + fractional_exp} = 2^integer_exp * 2^fractional_exp
// The max supported exponent is 2^10. If greater exponent is given, the function panics.
p0mvn marked this conversation as resolved.
Show resolved Hide resolved
// Panics if the exponent is negative.
func Exp2(exponent BigDec) BigDec {
if exponent.Abs().GT(maxSupportedExponent) {
panic(fmt.Sprintf("integer exponent %s is too large, max (%s)", exponent, maxSupportedExponent))
}
if exponent.IsNegative() {
panic(fmt.Sprintf("negative exponent %s is not supported", exponent))
}

integerExponentDec := exponent.TruncateDec()
integerExponent := integerExponentDec.TruncateInt()
integerResult := twoBigDec.PowerInteger(integerExponent.Uint64())
p0mvn marked this conversation as resolved.
Show resolved Hide resolved

fractionalExponent := exponent.Sub(integerExponentDec)
fractionalResult := exp2ChebyshevRationalApprox(fractionalExponent)

result := integerResult.Mul(fractionalResult)

return result
}

// exp2ChebyshevRationalApprox takes 2 to the power of a given decimal exponent.
// The result is approximated by a 13 parameter Chebyshev rational approximation.
// f(x) = h(x) / p(x) (7, 7) terms. We set the first term of p(x) to 1.
// As a result, this ends up being 7 + 6 = 13 parameters.
// The numerator coefficients are truncated at precision end. The denominator
// coefficients are rounded up at precision end.
// See scripts/approximations/README.md for details of the scripts used
// to compute the coefficients.
// CONTRACT: exponent must be in the range [0, 1], panics if not.
func exp2ChebyshevRationalApprox(x BigDec) BigDec {
if x.LT(ZeroDec()) || x.GT(OneDec()) {
panic(fmt.Sprintf("exponent must be in the range [0, 1], got %s", x))
}
if x.IsZero() {
return OneDec()
}
if x.Equal(OneDec()) {
return twoBigDec
}

h_x := numeratorCoefficients13Param[0].Clone()
p_x := denominatorCoefficients13Param[0].Clone()
x_exp_i := OneDec()
for i := 1; i < len(numeratorCoefficients13Param); i++ {
x_exp_i.MulMut(x)

h_x = h_x.Add(numeratorCoefficients13Param[i].Mul(x_exp_i))
p_x = p_x.Add(denominatorCoefficients13Param[i].Mul(x_exp_i))
}

return h_x.Quo(p_x)
p0mvn marked this conversation as resolved.
Show resolved Hide resolved
}
Loading