Repository: osmosis-labs/osmosis
Pull request #1231 (2022-04-11 13:24:19)

Add `BigInt`, `BigDec` to osmomath
<!-- < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < ☺
v                               ✰  Thanks for creating a PR! ✰    
v    Before smashing the submit button please review the checkboxes.
v    If a checkbox is n/a - please still include it but + a little note why
v    If your PR doesn't close an issue, that's OK!  Just remove the Closes: #XXX line!
☺ > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >  -->


## Description
This PR adds BigDec & BigInt to osmomath package. 
sdk.Dec is implemented upon sdk.Int, which has a max bit length of 256 bit. sdk.Dec would then have max bit of 256 + 60 = 316 bits. 

The implementation of BigInt and BigDec changes these constants in the way that BigInt has the max bit length of 512 bit, 
 max BigInt being 2^512-1 = 1.340781e+154, min BigInt = -(2^512-1) = -1.340781e+154

and BigDec having the max bit len of 572(512 + 60). 

The naming of BigInt does bring confusion with big.Int though, open to recommendations for namings for BigInt

______

For contributor use:

- [ ] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/gaia/blob/main/CONTRIBUTING.md#pr-targeting))
- [ ] Updated relevant documentation (`docs/`) or specification (`x/<module>/spec/`)
- [ ] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md`
- [ ] Re-reviewed `Files changed` in the Github PR explorer



ValarDragon: My main concern with an OSS library for BigDec is that we then have to deal with serialization overheads / longer term maintenance of that. I believe the SDK is working on trying to get a single serialization format for numeric types, and then have multiple decimal backends be deserializable from that. We'd have to maintain such a pairing if we adapted an open source lib like [apd](https://github.com/cockroachdb/apd) 
ValarDragon: Why do we have the `BigInt` file? I think we should just have `BigDec` right, and make all methods truncate to sdk.Int?
mattverse: sdk.Int is what contains the restriction for 256bit
mconcat: How do you think about adding bit precision parameter to NewInt/NewDec on SDK fork, and use it for precise computation purpose? So normally 256-bit NewInt will be constructed using `sdk.NewInt(3)`, but for different precision something like `sdk.NewIntBitPrec(3, 512)`.
mattverse: Yeah that was one of the main points per yesterday's discussion. I guess it's a trade off, one way or the other. With that being said, on the second thought, my thoughts are leaning towards @ValarDragon's opinion on using `BigDec` instead of fixing the original Dec type
fedekunze: curious why do you need to support a bigger bit len on Osmo
alexanderbez: > How do you think about adding bit precision parameter to NewInt/NewDec on SDK fork, and use it for precise computation purpose? So normally 256-bit NewInt will be constructed using `sdk.NewInt(3)`, but for different precision something like `sdk.NewIntBitPrec(3, 512)`.
> 
> (and make type alias for the 512-bit precision Int/Dec)

This was my proposal on the call.
alexanderbez: I'm not sure I follow? I think it's pretty straightforward actually. All you do is move the private bit variable internally into the `Dec` type directly. No APIs change. You only introduce a few new APIs and change a bit of the logic where the bit variable is used.
mattverse: Yeah your right. Changing Dec struct in sdk side looks like a more sustainable solution in software development pov.

I'm down to closing this PR and making changes in the sdk side, I'm also curious what @ValarDragon  thinks

alexanderbez: As long as it doesn't change the marshaling, which it doesn't AFAIK (but we should confirm via a simple unit test), then it should be pretty straight forward. But yeah I'm curious to see what @ValarDragon thinks.
ValarDragon: I'm not really in favor of doing a max precision field in the type itself. Its not how any other fixed precision integer libraries I've worked with before act, and I don't think its the right long term.

One thing thats common is templating / using a generic for fixed word sizes
mconcat: We can do something like
```go
type Int[T BitSize] struct {
  *big.Int
}

type BitSize interface {
  BitSize() int
}

type BitSize256 struct {}
func (BitSize256) BitSize() int { return 256 }

type BitSize512 struct {}
func (BitSize512) BitSize() int { return 512 }

// inside of sdk.Int usage...
func (i Int[T]) SomeFunc() {
  bitsize := T.BitSize()
}
```
ValarDragon: Yeah, I think thats the right generalized approach. Thats similar to what is done in Arkworks / other algebra libraries I'm familiar with. (https://github.com/arkworks-rs/algebra/blob/master/ff/src/biginteger/mod.rs#L25-L26) (If you look into the macros that implement these, are some optimizations for different word sizes / loop unrolling, but we likely won't need to chase speeds that fast)

I think we can leave that approach to much future work though.
alexanderbez: I like the generics approach @mattverse! We should make those changes in the SDK though :)

Patch:
diff --git a/osmomath/decimal.go b/osmomath/decimal.go
new file mode 100644
index 000000000..b082fad37
--- /dev/null
+++ b/osmomath/decimal.go
@@ -0,0 +1,802 @@
+package osmomath
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"math/big"
+	"strconv"
+	"strings"
+	"testing"
+)
+
+// NOTE: never use new(BigDec) or else we will panic unmarshalling into the
+// nil embedded big.Int
+type BigDec struct {
+	i *big.Int
+}
+
+const (
+	// number of decimal places
+	Precision = 18
+
+	// bytes required to represent the above precision
+	// Ceiling[Log2[999 999 999 999 999 999]]
+	DecimalPrecisionBits = 60
+
+	maxDecBitLen = maxBitLen + DecimalPrecisionBits
+
+	// max number of iterations in ApproxRoot function
+	maxApproxRootIterations = 100
+)
+
+var (
+	precisionReuse       = new(big.Int).Exp(big.NewInt(10), big.NewInt(Precision), nil)
+	fivePrecision        = new(big.Int).Quo(precisionReuse, big.NewInt(2))
+	precisionMultipliers []*big.Int
+	zeroInt              = big.NewInt(0)
+	oneInt               = big.NewInt(1)
+	tenInt               = big.NewInt(10)
+)
+
+// Decimal errors
+var (
+	ErrEmptyDecimalStr      = errors.New("decimal string cannot be empty")
+	ErrInvalidDecimalLength = errors.New("invalid decimal length")
+	ErrInvalidDecimalStr    = errors.New("invalid decimal string")
+)
+
+// Set precision multipliers
+func init() {
+	precisionMultipliers = make([]*big.Int, Precision+1)
+	for i := 0; i <= Precision; i++ {
+		precisionMultipliers[i] = calcPrecisionMultiplier(int64(i))
+	}
+}
+
+func precisionInt() *big.Int {
+	return new(big.Int).Set(precisionReuse)
+}
+
+func ZeroDec() BigDec     { return BigDec{new(big.Int).Set(zeroInt)} }
+func OneDec() BigDec      { return BigDec{precisionInt()} }
+func SmallestDec() BigDec { return BigDec{new(big.Int).Set(oneInt)} }
+
+// calculate the precision multiplier
+func calcPrecisionMultiplier(prec int64) *big.Int {
+	if prec > Precision {
+		panic(fmt.Sprintf("too much precision, maximum %v, provided %v", Precision, prec))
+	}
+	zerosToAdd := Precision - prec
+	multiplier := new(big.Int).Exp(tenInt, big.NewInt(zerosToAdd), nil)
+	return multiplier
+}
+
+// get the precision multiplier, do not mutate result
+func precisionMultiplier(prec int64) *big.Int {
+	if prec > Precision {
+		panic(fmt.Sprintf("too much precision, maximum %v, provided %v", Precision, prec))
+	}
+	return precisionMultipliers[prec]
+}
+
+// create a new NewBigDec from integer assuming whole number
+func NewBigDec(i int64) BigDec {
+	return NewDecWithPrec(i, 0)
+}
+
+// create a new BigDec from integer with decimal place at prec
+// CONTRACT: prec <= Precision
+func NewDecWithPrec(i, prec int64) BigDec {
+	return BigDec{
+		new(big.Int).Mul(big.NewInt(i), precisionMultiplier(prec)),
+	}
+}
+
+// create a new BigDec from big integer assuming whole numbers
+// CONTRACT: prec <= Precision
+func NewDecFromBigInt(i *big.Int) BigDec {
+	return NewDecFromBigIntWithPrec(i, 0)
+}
+
+// create a new BigDec from big integer assuming whole numbers
+// CONTRACT: prec <= Precision
+func NewDecFromBigIntWithPrec(i *big.Int, prec int64) BigDec {
+	return BigDec{
+		new(big.Int).Mul(i, precisionMultiplier(prec)),
+	}
+}
+
+// create a new BigDec from big integer assuming whole numbers
+// CONTRACT: prec <= Precision
+func NewDecFromInt(i BigInt) BigDec {
+	return NewDecFromIntWithPrec(i, 0)
+}
+
+// create a new BigDec from big integer with decimal place at prec
+// CONTRACT: prec <= Precision
+func NewDecFromIntWithPrec(i BigInt, prec int64) BigDec {
+	return BigDec{
+		new(big.Int).Mul(i.BigInt(), precisionMultiplier(prec)),
+	}
+}
+
+// create a decimal from an input decimal string.
+// valid must come in the form:
+//   (-) whole integers (.) decimal integers
+// examples of acceptable input include:
+//   -123.456
+//   456.7890
+//   345
+//   -456789
+//
+// NOTE - An error will return if more decimal places
+// are provided in the string than the constant Precision.
+//
+// CONTRACT - This function does not mutate the input str.
+func NewDecFromStr(str string) (BigDec, error) {
+	if len(str) == 0 {
+		return BigDec{}, ErrEmptyDecimalStr
+	}
+
+	// first extract any negative symbol
+	neg := false
+	if str[0] == '-' {
+		neg = true
+		str = str[1:]
+	}
+
+	if len(str) == 0 {
+		return BigDec{}, ErrEmptyDecimalStr
+	}
+
+	strs := strings.Split(str, ".")
+	lenDecs := 0
+	combinedStr := strs[0]
+
+	if len(strs) == 2 { // has a decimal place
+		lenDecs = len(strs[1])
+		if lenDecs == 0 || len(combinedStr) == 0 {
+			return BigDec{}, ErrInvalidDecimalLength
+		}
+		combinedStr += strs[1]
+	} else if len(strs) > 2 {
+		return BigDec{}, ErrInvalidDecimalStr
+	}
+
+	if lenDecs > Precision {
+		return BigDec{}, fmt.Errorf("invalid precision; max: %d, got: %d", Precision, lenDecs)
+	}
+
+	// add some extra zero's to correct to the Precision factor
+	zerosToAdd := Precision - lenDecs
+	zeros := fmt.Sprintf(`%0`+strconv.Itoa(zerosToAdd)+`s`, "")
+	combinedStr += zeros
+
+	combined, ok := new(big.Int).SetString(combinedStr, 10) // base 10
+	if !ok {
+		return BigDec{}, fmt.Errorf("failed to set decimal string: %s", combinedStr)
+	}
+	if combined.BitLen() > maxBitLen {
+		return BigDec{}, fmt.Errorf("decimal out of range; bitLen: got %d, max %d", combined.BitLen(), maxBitLen)
+	}
+	if neg {
+		combined = new(big.Int).Neg(combined)
+	}
+
+	return BigDec{combined}, nil
+}
+
+// Decimal from string, panic on error
+func MustNewDecFromStr(s string) BigDec {
+	dec, err := NewDecFromStr(s)
+	if err != nil {
+		panic(err)
+	}
+	return dec
+}
+
+func (d BigDec) IsNil() bool          { return d.i == nil }                    // is decimal nil
+func (d BigDec) IsZero() bool         { return (d.i).Sign() == 0 }             // is equal to zero
+func (d BigDec) IsNegative() bool     { return (d.i).Sign() == -1 }            // is negative
+func (d BigDec) IsPositive() bool     { return (d.i).Sign() == 1 }             // is positive
+func (d BigDec) Equal(d2 BigDec) bool { return (d.i).Cmp(d2.i) == 0 }          // equal decimals
+func (d BigDec) GT(d2 BigDec) bool    { return (d.i).Cmp(d2.i) > 0 }           // greater than
+func (d BigDec) GTE(d2 BigDec) bool   { return (d.i).Cmp(d2.i) >= 0 }          // greater than or equal
+func (d BigDec) LT(d2 BigDec) bool    { return (d.i).Cmp(d2.i) < 0 }           // less than
+func (d BigDec) LTE(d2 BigDec) bool   { return (d.i).Cmp(d2.i) <= 0 }          // less than or equal
+func (d BigDec) Neg() BigDec          { return BigDec{new(big.Int).Neg(d.i)} } // reverse the decimal sign
+func (d BigDec) Abs() BigDec          { return BigDec{new(big.Int).Abs(d.i)} } // absolute value
+
+// BigInt returns a copy of the underlying big.Int.
+func (d BigDec) BigInt() *big.Int {
+	if d.IsNil() {
+		return nil
+	}
+
+	cp := new(big.Int)
+	return cp.Set(d.i)
+}
+
+// addition
+func (d BigDec) Add(d2 BigDec) BigDec {
+	res := new(big.Int).Add(d.i, d2.i)
+
+	if res.BitLen() > maxDecBitLen {
+		panic("Int overflow")
+	}
+	return BigDec{res}
+}
+
+// subtraction
+func (d BigDec) Sub(d2 BigDec) BigDec {
+	res := new(big.Int).Sub(d.i, d2.i)
+
+	if res.BitLen() > maxDecBitLen {
+		panic("Int overflow")
+	}
+	return BigDec{res}
+}
+
+// multiplication
+func (d BigDec) Mul(d2 BigDec) BigDec {
+	mul := new(big.Int).Mul(d.i, d2.i)
+	chopped := chopPrecisionAndRound(mul)
+
+	if chopped.BitLen() > maxDecBitLen {
+		panic("Int overflow")
+	}
+	return BigDec{chopped}
+}
+
+// multiplication truncate
+func (d BigDec) MulTruncate(d2 BigDec) BigDec {
+	mul := new(big.Int).Mul(d.i, d2.i)
+	chopped := chopPrecisionAndTruncate(mul)
+
+	if chopped.BitLen() > maxDecBitLen {
+		panic("Int overflow")
+	}
+	return BigDec{chopped}
+}
+
+// multiplication
+func (d BigDec) MulInt(i BigInt) BigDec {
+	mul := new(big.Int).Mul(d.i, i.i)
+
+	if mul.BitLen() > maxDecBitLen {
+		panic("Int overflow")
+	}
+	return BigDec{mul}
+}
+
+// MulInt64 - multiplication with int64
+func (d BigDec) MulInt64(i int64) BigDec {
+	mul := new(big.Int).Mul(d.i, big.NewInt(i))
+
+	if mul.BitLen() > maxDecBitLen {
+		panic("Int overflow")
+	}
+	return BigDec{mul}
+}
+
+// quotient
+func (d BigDec) Quo(d2 BigDec) BigDec {
+	// multiply precision twice
+	mul := new(big.Int).Mul(d.i, precisionReuse)
+	mul.Mul(mul, precisionReuse)
+
+	quo := new(big.Int).Quo(mul, d2.i)
+	chopped := chopPrecisionAndRound(quo)
+
+	if chopped.BitLen() > maxDecBitLen {
+		panic("Int overflow")
+	}
+	return BigDec{chopped}
+}
+
+// quotient truncate
+func (d BigDec) QuoTruncate(d2 BigDec) BigDec {
+	// multiply precision twice
+	mul := new(big.Int).Mul(d.i, precisionReuse)
+	mul.Mul(mul, precisionReuse)
+
+	quo := mul.Quo(mul, d2.i)
+	chopped := chopPrecisionAndTruncate(quo)
+
+	if chopped.BitLen() > maxDecBitLen {
+		panic("Int overflow")
+	}
+	return BigDec{chopped}
+}
+
+// quotient, round up
+func (d BigDec) QuoRoundUp(d2 BigDec) BigDec {
+	// multiply precision twice
+	mul := new(big.Int).Mul(d.i, precisionReuse)
+	mul.Mul(mul, precisionReuse)
+
+	quo := new(big.Int).Quo(mul, d2.i)
+	chopped := chopPrecisionAndRoundUp(quo)
+
+	if chopped.BitLen() > maxDecBitLen {
+		panic("Int overflow")
+	}
+	return BigDec{chopped}
+}
+
+// quotient
+func (d BigDec) QuoInt(i BigInt) BigDec {
+	mul := new(big.Int).Quo(d.i, i.i)
+	return BigDec{mul}
+}
+
+// QuoInt64 - quotient with int64
+func (d BigDec) QuoInt64(i int64) BigDec {
+	mul := new(big.Int).Quo(d.i, big.NewInt(i))
+	return BigDec{mul}
+}
+
+// ApproxRoot returns an approximate estimation of a Dec's positive real nth root
+// using Newton's method (where n is positive). The algorithm starts with some guess and
+// computes the sequence of improved guesses until an answer converges to an
+// approximate answer.  It returns `|d|.ApproxRoot() * -1` if input is negative.
+// A maximum number of 100 iterations is used a backup boundary condition for
+// cases where the answer never converges enough to satisfy the main condition.
+func (d BigDec) ApproxRoot(root uint64) (guess BigDec, err error) {
+	defer func() {
+		if r := recover(); r != nil {
+			var ok bool
+			err, ok = r.(error)
+			if !ok {
+				err = errors.New("out of bounds")
+			}
+		}
+	}()
+
+	if d.IsNegative() {
+		absRoot, err := d.MulInt64(-1).ApproxRoot(root)
+		return absRoot.MulInt64(-1), err
+	}
+
+	if root == 1 || d.IsZero() || d.Equal(OneDec()) {
+		return d, nil
+	}
+
+	if root == 0 {
+		return OneDec(), nil
+	}
+
+	rootInt := NewIntFromUint64(root)
+	guess, delta := OneDec(), OneDec()
+
+	for iter := 0; delta.Abs().GT(SmallestDec()) && iter < maxApproxRootIterations; iter++ {
+		prev := guess.Power(root - 1)
+		if prev.IsZero() {
+			prev = SmallestDec()
+		}
+		delta = d.Quo(prev)
+		delta = delta.Sub(guess)
+		delta = delta.QuoInt(rootInt)
+
+		guess = guess.Add(delta)
+	}
+
+	return guess, nil
+}
+
+// Power returns a the result of raising to a positive integer power
+func (d BigDec) Power(power uint64) BigDec {
+	if power == 0 {
+		return OneDec()
+	}
+	tmp := OneDec()
+
+	for i := power; i > 1; {
+		if i%2 != 0 {
+			tmp = tmp.Mul(d)
+		}
+		i /= 2
+		d = d.Mul(d)
+	}
+
+	return d.Mul(tmp)
+}
+
+// ApproxSqrt is a wrapper around ApproxRoot for the common special case
+// of finding the square root of a number. It returns -(sqrt(abs(d)) if input is negative.
+func (d BigDec) ApproxSqrt() (BigDec, error) {
+	return d.ApproxRoot(2)
+}
+
+// is integer, e.g. decimals are zero
+func (d BigDec) IsInteger() bool {
+	return new(big.Int).Rem(d.i, precisionReuse).Sign() == 0
+}
+
+// format decimal state
+func (d BigDec) Format(s fmt.State, verb rune) {
+	_, err := s.Write([]byte(d.String()))
+	if err != nil {
+		panic(err)
+	}
+}
+
+func (d BigDec) String() string {
+	if d.i == nil {
+		return d.i.String()
+	}
+
+	isNeg := d.IsNegative()
+
+	if isNeg {
+		d = d.Neg()
+	}
+
+	bzInt, err := d.i.MarshalText()
+	if err != nil {
+		return ""
+	}
+	inputSize := len(bzInt)
+
+	var bzStr []byte
+
+	// TODO: Remove trailing zeros
+	// case 1, purely decimal
+	if inputSize <= Precision {
+		bzStr = make([]byte, Precision+2)
+
+		// 0. prefix
+		bzStr[0] = byte('0')
+		bzStr[1] = byte('.')
+
+		// set relevant digits to 0
+		for i := 0; i < Precision-inputSize; i++ {
+			bzStr[i+2] = byte('0')
+		}
+
+		// set final digits
+		copy(bzStr[2+(Precision-inputSize):], bzInt)
+	} else {
+		// inputSize + 1 to account for the decimal point that is being added
+		bzStr = make([]byte, inputSize+1)
+		decPointPlace := inputSize - Precision
+
+		copy(bzStr, bzInt[:decPointPlace])                   // pre-decimal digits
+		bzStr[decPointPlace] = byte('.')                     // decimal point
+		copy(bzStr[decPointPlace+1:], bzInt[decPointPlace:]) // post-decimal digits
+	}
+
+	if isNeg {
+		return "-" + string(bzStr)
+	}
+
+	return string(bzStr)
+}
+
+// Float64 returns the float64 representation of a BigDec.
+// Will return the error if the conversion failed.
+func (d BigDec) Float64() (float64, error) {
+	return strconv.ParseFloat(d.String(), 64)
+}
+
+// MustFloat64 returns the float64 representation of a BigDec.
+// Would panic if the conversion failed.
+func (d BigDec) MustFloat64() float64 {
+	if value, err := strconv.ParseFloat(d.String(), 64); err != nil {
+		panic(err)
+	} else {
+		return value
+	}
+}
+
+//     ____
+//  __|    |__   "chop 'em
+//       ` \     round!"
+// ___||  ~  _     -bankers
+// |         |      __
+// |       | |   __|__|__
+// |_____:  /   | $$$    |
+//              |________|
+
+// Remove a Precision amount of rightmost digits and perform bankers rounding
+// on the remainder (gaussian rounding) on the digits which have been removed.
+//
+// Mutates the input. Use the non-mutative version if that is undesired
+func chopPrecisionAndRound(d *big.Int) *big.Int {
+	// remove the negative and add it back when returning
+	if d.Sign() == -1 {
+		// make d positive, compute chopped value, and then un-mutate d
+		d = d.Neg(d)
+		d = chopPrecisionAndRound(d)
+		d = d.Neg(d)
+		return d
+	}
+
+	// get the truncated quotient and remainder
+	quo, rem := d, big.NewInt(0)
+	quo, rem = quo.QuoRem(d, precisionReuse, rem)
+
+	if rem.Sign() == 0 { // remainder is zero
+		return quo
+	}
+
+	switch rem.Cmp(fivePrecision) {
+	case -1:
+		return quo
+	case 1:
+		return quo.Add(quo, oneInt)
+	default: // bankers rounding must take place
+		// always round to an even number
+		if quo.Bit(0) == 0 {
+			return quo
+		}
+		return quo.Add(quo, oneInt)
+	}
+}
+
+func chopPrecisionAndRoundUp(d *big.Int) *big.Int {
+	// remove the negative and add it back when returning
+	if d.Sign() == -1 {
+		// make d positive, compute chopped value, and then un-mutate d
+		d = d.Neg(d)
+		// truncate since d is negative...
+		d = chopPrecisionAndTruncate(d)
+		d = d.Neg(d)
+		return d
+	}
+
+	// get the truncated quotient and remainder
+	quo, rem := d, big.NewInt(0)
+	quo, rem = quo.QuoRem(d, precisionReuse, rem)
+
+	if rem.Sign() == 0 { // remainder is zero
+		return quo
+	}
+
+	return quo.Add(quo, oneInt)
+}
+
+func chopPrecisionAndRoundNonMutative(d *big.Int) *big.Int {
+	tmp := new(big.Int).Set(d)
+	return chopPrecisionAndRound(tmp)
+}
+
+// RoundInt64 rounds the decimal using bankers rounding
+func (d BigDec) RoundInt64() int64 {
+	chopped := chopPrecisionAndRoundNonMutative(d.i)
+	if !chopped.IsInt64() {
+		panic("Int64() out of bound")
+	}
+	return chopped.Int64()
+}
+
+// RoundInt round the decimal using bankers rounding
+func (d BigDec) RoundInt() BigInt {
+	return NewIntFromBigInt(chopPrecisionAndRoundNonMutative(d.i))
+}
+
+// chopPrecisionAndTruncate is similar to chopPrecisionAndRound,
+// but always rounds down. It does not mutate the input.
+func chopPrecisionAndTruncate(d *big.Int) *big.Int {
+	return new(big.Int).Quo(d, precisionReuse)
+}
+
+// TruncateInt64 truncates the decimals from the number and returns an int64
+func (d BigDec) TruncateInt64() int64 {
+	chopped := chopPrecisionAndTruncate(d.i)
+	if !chopped.IsInt64() {
+		panic("Int64() out of bound")
+	}
+	return chopped.Int64()
+}
+
+// TruncateInt truncates the decimals from the number and returns an Int
+func (d BigDec) TruncateInt() BigInt {
+	return NewIntFromBigInt(chopPrecisionAndTruncate(d.i))
+}
+
+// TruncateDec truncates the decimals from the number and returns a Dec
+func (d BigDec) TruncateDec() BigDec {
+	return NewDecFromBigInt(chopPrecisionAndTruncate(d.i))
+}
+
+// Ceil returns the smallest interger value (as a decimal) that is greater than
+// or equal to the given decimal.
+func (d BigDec) Ceil() BigDec {
+	tmp := new(big.Int).Set(d.i)
+
+	quo, rem := tmp, big.NewInt(0)
+	quo, rem = quo.QuoRem(tmp, precisionReuse, rem)
+
+	// no need to round with a zero remainder regardless of sign
+	if rem.Cmp(zeroInt) == 0 {
+		return NewDecFromBigInt(quo)
+	}
+
+	if rem.Sign() == -1 {
+		return NewDecFromBigInt(quo)
+	}
+
+	return NewDecFromBigInt(quo.Add(quo, oneInt))
+}
+
+// MaxSortableDec is the largest Dec that can be passed into SortableDecBytes()
+// Its negative form is the least Dec that can be passed in.
+var MaxSortableDec = OneDec().Quo(SmallestDec())
+
+// ValidSortableDec ensures that a Dec is within the sortable bounds,
+// a BigDec can't have a precision of less than 10^-18.
+// Max sortable decimal was set to the reciprocal of SmallestDec.
+func ValidSortableDec(dec BigDec) bool {
+	return dec.Abs().LTE(MaxSortableDec)
+}
+
+// SortableDecBytes returns a byte slice representation of a Dec that can be sorted.
+// Left and right pads with 0s so there are 18 digits to left and right of the decimal point.
+// For this reason, there is a maximum and minimum value for this, enforced by ValidSortableDec.
+func SortableDecBytes(dec BigDec) []byte {
+	if !ValidSortableDec(dec) {
+		panic("dec must be within bounds")
+	}
+	// Instead of adding an extra byte to all sortable decs in order to handle max sortable, we just
+	// makes its bytes be "max" which comes after all numbers in ASCIIbetical order
+	if dec.Equal(MaxSortableDec) {
+		return []byte("max")
+	}
+	// For the same reason, we make the bytes of minimum sortable dec be --, which comes before all numbers.
+	if dec.Equal(MaxSortableDec.Neg()) {
+		return []byte("--")
+	}
+	// We move the negative sign to the front of all the left padded 0s, to make negative numbers come before positive numbers
+	if dec.IsNegative() {
+		return append([]byte("-"), []byte(fmt.Sprintf(fmt.Sprintf("%%0%ds", Precision*2+1), dec.Abs().String()))...)
+	}
+	return []byte(fmt.Sprintf(fmt.Sprintf("%%0%ds", Precision*2+1), dec.String()))
+}
+
+// reuse nil values
+var nilJSON []byte
+
+func init() {
+	empty := new(big.Int)
+	bz, _ := empty.MarshalText()
+	nilJSON, _ = json.Marshal(string(bz))
+}
+
+// MarshalJSON marshals the decimal
+func (d BigDec) MarshalJSON() ([]byte, error) {
+	if d.i == nil {
+		return nilJSON, nil
+	}
+	return json.Marshal(d.String())
+}
+
+// UnmarshalJSON defines custom decoding scheme
+func (d *BigDec) UnmarshalJSON(bz []byte) error {
+	if d.i == nil {
+		d.i = new(big.Int)
+	}
+
+	var text string
+	err := json.Unmarshal(bz, &text)
+	if err != nil {
+		return err
+	}
+
+	// TODO: Reuse dec allocation
+	newDec, err := NewDecFromStr(text)
+	if err != nil {
+		return err
+	}
+
+	d.i = newDec.i
+	return nil
+}
+
+// MarshalYAML returns the YAML representation.
+func (d BigDec) MarshalYAML() (interface{}, error) {
+	return d.String(), nil
+}
+
+// Marshal implements the gogo proto custom type interface.
+func (d BigDec) Marshal() ([]byte, error) {
+	if d.i == nil {
+		d.i = new(big.Int)
+	}
+	return d.i.MarshalText()
+}
+
+// MarshalTo implements the gogo proto custom type interface.
+func (d *BigDec) MarshalTo(data []byte) (n int, err error) {
+	if d.i == nil {
+		d.i = new(big.Int)
+	}
+
+	if d.i.Cmp(zeroInt) == 0 {
+		copy(data, []byte{0x30})
+		return 1, nil
+	}
+
+	bz, err := d.Marshal()
+	if err != nil {
+		return 0, err
+	}
+
+	copy(data, bz)
+	return len(bz), nil
+}
+
+// Unmarshal implements the gogo proto custom type interface.
+func (d *BigDec) Unmarshal(data []byte) error {
+	if len(data) == 0 {
+		d = nil
+		return nil
+	}
+
+	if d.i == nil {
+		d.i = new(big.Int)
+	}
+
+	if err := d.i.UnmarshalText(data); err != nil {
+		return err
+	}
+
+	if d.i.BitLen() > maxBitLen {
+		return fmt.Errorf("decimal out of range; got: %d, max: %d", d.i.BitLen(), maxBitLen)
+	}
+
+	return nil
+}
+
+// Size implements the gogo proto custom type interface.
+func (d *BigDec) Size() int {
+	bz, _ := d.Marshal()
+	return len(bz)
+}
+
+// Override Amino binary serialization by proxying to protobuf.
+func (d BigDec) MarshalAmino() ([]byte, error)   { return d.Marshal() }
+func (d *BigDec) UnmarshalAmino(bz []byte) error { return d.Unmarshal(bz) }
+
+// helpers
+
+// test if two decimal arrays are equal
+func DecsEqual(d1s, d2s []BigDec) bool {
+	if len(d1s) != len(d2s) {
+		return false
+	}
+
+	for i, d1 := range d1s {
+		if !d1.Equal(d2s[i]) {
+			return false
+		}
+	}
+	return true
+}
+
+// minimum decimal between two
+func MinDec(d1, d2 BigDec) BigDec {
+	if d1.LT(d2) {
+		return d1
+	}
+	return d2
+}
+
+// maximum decimal between two
+func MaxDec(d1, d2 BigDec) BigDec {
+	if d1.LT(d2) {
+		return d2
+	}
+	return d1
+}
+
+// intended to be used with require/assert:  require.True(DecEq(...))
+func DecEq(t *testing.T, exp, got BigDec) (*testing.T, bool, string, string, string) {
+	return t, exp.Equal(got), "expected:\t%v\ngot:\t\t%v", exp.String(), got.String()
+}
+
+func DecApproxEq(t *testing.T, d1 BigDec, d2 BigDec, tol BigDec) (*testing.T, bool, string, string, string) {
+	diff := d1.Sub(d2).Abs()
+	return t, diff.LTE(tol), "expected |d1 - d2| <:\t%v\ngot |d1 - d2| = \t\t%v", tol.String(), diff.String()
+}
diff --git a/osmomath/int.go b/osmomath/int.go
new file mode 100644
index 000000000..1be621e78
--- /dev/null
+++ b/osmomath/int.go
@@ -0,0 +1,437 @@
+package osmomath
+
+import (
+	"encoding"
+	"encoding/json"
+	"fmt"
+	"testing"
+
+	"math/big"
+)
+
+const maxBitLen = 512
+
+func newIntegerFromString(s string) (*big.Int, bool) {
+	return new(big.Int).SetString(s, 0)
+}
+
+func equal(i *big.Int, i2 *big.Int) bool { return i.Cmp(i2) == 0 }
+
+func gt(i *big.Int, i2 *big.Int) bool { return i.Cmp(i2) == 1 }
+
+func gte(i *big.Int, i2 *big.Int) bool { return i.Cmp(i2) >= 0 }
+
+func lt(i *big.Int, i2 *big.Int) bool { return i.Cmp(i2) == -1 }
+
+func lte(i *big.Int, i2 *big.Int) bool { return i.Cmp(i2) <= 0 }
+
+func add(i *big.Int, i2 *big.Int) *big.Int { return new(big.Int).Add(i, i2) }
+
+func sub(i *big.Int, i2 *big.Int) *big.Int { return new(big.Int).Sub(i, i2) }
+
+func mul(i *big.Int, i2 *big.Int) *big.Int { return new(big.Int).Mul(i, i2) }
+
+func div(i *big.Int, i2 *big.Int) *big.Int { return new(big.Int).Quo(i, i2) }
+
+func mod(i *big.Int, i2 *big.Int) *big.Int { return new(big.Int).Mod(i, i2) }
+
+func neg(i *big.Int) *big.Int { return new(big.Int).Neg(i) }
+
+func abs(i *big.Int) *big.Int { return new(big.Int).Abs(i) }
+
+func min(i *big.Int, i2 *big.Int) *big.Int {
+	if i.Cmp(i2) == 1 {
+		return new(big.Int).Set(i2)
+	}
+
+	return new(big.Int).Set(i)
+}
+
+func max(i *big.Int, i2 *big.Int) *big.Int {
+	if i.Cmp(i2) == -1 {
+		return new(big.Int).Set(i2)
+	}
+
+	return new(big.Int).Set(i)
+}
+
+func unmarshalText(i *big.Int, text string) error {
+	if err := i.UnmarshalText([]byte(text)); err != nil {
+		return err
+	}
+
+	if i.BitLen() > maxBitLen {
+		return fmt.Errorf("integer out of range: %s", text)
+	}
+
+	return nil
+}
+
+// Int wraps big.Int with a 257 bit range bound
+// Checks overflow, underflow and division by zero
+// Exists in range from -(2^256 - 1) to 2^256 - 1
+type BigInt struct {
+	i *big.Int
+}
+
+// BigInt converts Int to big.Int
+func (i BigInt) BigInt() *big.Int {
+	if i.IsNil() {
+		return nil
+	}
+	return new(big.Int).Set(i.i)
+}
+
+// IsNil returns true if Int is uninitialized
+func (i BigInt) IsNil() bool {
+	return i.i == nil
+}
+
+// NewInt constructs Int from int64
+func NewInt(n int64) BigInt {
+	return BigInt{big.NewInt(n)}
+}
+
+// NewIntFromUint64 constructs an Int from a uint64.
+func NewIntFromUint64(n uint64) BigInt {
+	b := big.NewInt(0)
+	b.SetUint64(n)
+	return BigInt{b}
+}
+
+// NewIntFromBigInt constructs Int from big.Int. If the provided big.Int is nil,
+// it returns an empty instance. This function panics if the bit length is > 256.
+func NewIntFromBigInt(i *big.Int) BigInt {
+	if i == nil {
+		return BigInt{}
+	}
+
+	if i.BitLen() > maxBitLen {
+		panic("NewIntFromBigInt() out of bound")
+	}
+	return BigInt{i}
+}
+
+// NewIntFromString constructs Int from string
+func NewIntFromString(s string) (res BigInt, ok bool) {
+	i, ok := newIntegerFromString(s)
+	if !ok {
+		return
+	}
+	// Check overflow
+	if i.BitLen() > maxBitLen {
+		ok = false
+		return
+	}
+	return BigInt{i}, true
+}
+
+// NewIntWithDecimal constructs Int with decimal
+// Result value is n*10^dec
+func NewIntWithDecimal(n int64, dec int) BigInt {
+	if dec < 0 {
+		panic("NewIntWithDecimal() decimal is negative")
+	}
+	exp := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(dec)), nil)
+	i := new(big.Int)
+	i.Mul(big.NewInt(n), exp)
+
+	// Check overflow
+	if i.BitLen() > maxBitLen {
+		panic("NewIntWithDecimal() out of bound")
+	}
+	return BigInt{i}
+}
+
+// ZeroInt returns Int value with zero
+func ZeroInt() BigInt { return BigInt{big.NewInt(0)} }
+
+// OneInt returns Int value with one
+func OneInt() BigInt { return BigInt{big.NewInt(1)} }
+
+// ToDec converts Int to Dec
+func (i BigInt) ToDec() BigDec {
+	return NewDecFromInt(i)
+}
+
+// Int64 converts Int to int64
+// Panics if the value is out of range
+func (i BigInt) Int64() int64 {
+	if !i.i.IsInt64() {
+		panic("Int64() out of bound")
+	}
+	return i.i.Int64()
+}
+
+// IsInt64 returns true if Int64() not panics
+func (i BigInt) IsInt64() bool {
+	return i.i.IsInt64()
+}
+
+// Uint64 converts Int to uint64
+// Panics if the value is out of range
+func (i BigInt) Uint64() uint64 {
+	if !i.i.IsUint64() {
+		panic("Uint64() out of bounds")
+	}
+	return i.i.Uint64()
+}
+
+// IsUint64 returns true if Uint64() not panics
+func (i BigInt) IsUint64() bool {
+	return i.i.IsUint64()
+}
+
+// IsZero returns true if Int is zero
+func (i BigInt) IsZero() bool {
+	return i.i.Sign() == 0
+}
+
+// IsNegative returns true if Int is negative
+func (i BigInt) IsNegative() bool {
+	return i.i.Sign() == -1
+}
+
+// IsPositive returns true if Int is positive
+func (i BigInt) IsPositive() bool {
+	return i.i.Sign() == 1
+}
+
+// Sign returns sign of Int
+func (i BigInt) Sign() int {
+	return i.i.Sign()
+}
+
+// Equal compares two Ints
+func (i BigInt) Equal(i2 BigInt) bool {
+	return equal(i.i, i2.i)
+}
+
+// GT returns true if first Int is greater than second
+func (i BigInt) GT(i2 BigInt) bool {
+	return gt(i.i, i2.i)
+}
+
+// GTE returns true if receiver Int is greater than or equal to the parameter
+// Int.
+func (i BigInt) GTE(i2 BigInt) bool {
+	return gte(i.i, i2.i)
+}
+
+// LT returns true if first Int is lesser than second
+func (i BigInt) LT(i2 BigInt) bool {
+	return lt(i.i, i2.i)
+}
+
+// LTE returns true if first Int is less than or equal to second
+func (i BigInt) LTE(i2 BigInt) bool {
+	return lte(i.i, i2.i)
+}
+
+// Add adds Int from another
+func (i BigInt) Add(i2 BigInt) (res BigInt) {
+	res = BigInt{add(i.i, i2.i)}
+	// Check overflow
+	if res.i.BitLen() > maxBitLen {
+		panic("Int overflow")
+	}
+	return
+}
+
+// AddRaw adds int64 to Int
+func (i BigInt) AddRaw(i2 int64) BigInt {
+	return i.Add(NewInt(i2))
+}
+
+// Sub subtracts Int from another
+func (i BigInt) Sub(i2 BigInt) (res BigInt) {
+	res = BigInt{sub(i.i, i2.i)}
+	// Check overflow
+	if res.i.BitLen() > maxBitLen {
+		panic("Int overflow")
+	}
+	return
+}
+
+// SubRaw subtracts int64 from Int
+func (i BigInt) SubRaw(i2 int64) BigInt {
+	return i.Sub(NewInt(i2))
+}
+
+// Mul multiples two Ints
+func (i BigInt) Mul(i2 BigInt) (res BigInt) {
+	// Check overflow
+	if i.i.BitLen()+i2.i.BitLen()-1 > maxBitLen {
+		panic("Int overflow")
+	}
+	res = BigInt{mul(i.i, i2.i)}
+	// Check overflow if sign of both are same
+	if res.i.BitLen() > maxBitLen {
+		panic("Int overflow")
+	}
+	return
+}
+
+// MulRaw multipies Int and int64
+func (i BigInt) MulRaw(i2 int64) BigInt {
+	return i.Mul(NewInt(i2))
+}
+
+// Quo divides Int with Int
+func (i BigInt) Quo(i2 BigInt) (res BigInt) {
+	// Check division-by-zero
+	if i2.i.Sign() == 0 {
+		panic("Division by zero")
+	}
+	return BigInt{div(i.i, i2.i)}
+}
+
+// QuoRaw divides Int with int64
+func (i BigInt) QuoRaw(i2 int64) BigInt {
+	return i.Quo(NewInt(i2))
+}
+
+// Mod returns remainder after dividing with Int
+func (i BigInt) Mod(i2 BigInt) BigInt {
+	if i2.Sign() == 0 {
+		panic("division-by-zero")
+	}
+	return BigInt{mod(i.i, i2.i)}
+}
+
+// ModRaw returns remainder after dividing with int64
+func (i BigInt) ModRaw(i2 int64) BigInt {
+	return i.Mod(NewInt(i2))
+}
+
+// Neg negates Int
+func (i BigInt) Neg() (res BigInt) {
+	return BigInt{neg(i.i)}
+}
+
+// Abs returns the absolute value of Int.
+func (i BigInt) Abs() BigInt {
+	return BigInt{abs(i.i)}
+}
+
+// return the minimum of the ints
+func MinInt(i1, i2 BigInt) BigInt {
+	return BigInt{min(i1.BigInt(), i2.BigInt())}
+}
+
+// MaxInt returns the maximum between two integers.
+func MaxInt(i, i2 BigInt) BigInt {
+	return BigInt{max(i.BigInt(), i2.BigInt())}
+}
+
+// Human readable string
+func (i BigInt) String() string {
+	return i.i.String()
+}
+
+// MarshalJSON defines custom encoding scheme
+func (i BigInt) MarshalJSON() ([]byte, error) {
+	if i.i == nil { // Necessary since default Uint initialization has i.i as nil
+		i.i = new(big.Int)
+	}
+	return marshalJSON(i.i)
+}
+
+// UnmarshalJSON defines custom decoding scheme
+func (i *BigInt) UnmarshalJSON(bz []byte) error {
+	if i.i == nil { // Necessary since default Int initialization has i.i as nil
+		i.i = new(big.Int)
+	}
+	return unmarshalJSON(i.i, bz)
+}
+
+// MarshalJSON for custom encoding scheme
+// Must be encoded as a string for JSON precision
+func marshalJSON(i encoding.TextMarshaler) ([]byte, error) {
+	text, err := i.MarshalText()
+	if err != nil {
+		return nil, err
+	}
+
+	return json.Marshal(string(text))
+}
+
+// UnmarshalJSON for custom decoding scheme
+// Must be encoded as a string for JSON precision
+func unmarshalJSON(i *big.Int, bz []byte) error {
+	var text string
+	if err := json.Unmarshal(bz, &text); err != nil {
+		return err
+	}
+
+	return unmarshalText(i, text)
+}
+
+// MarshalYAML returns the YAML representation.
+func (i BigInt) MarshalYAML() (interface{}, error) {
+	return i.String(), nil
+}
+
+// Marshal implements the gogo proto custom type interface.
+func (i BigInt) Marshal() ([]byte, error) {
+	if i.i == nil {
+		i.i = new(big.Int)
+	}
+	return i.i.MarshalText()
+}
+
+// MarshalTo implements the gogo proto custom type interface.
+func (i *BigInt) MarshalTo(data []byte) (n int, err error) {
+	if i.i == nil {
+		i.i = new(big.Int)
+	}
+	if i.i.BitLen() == 0 { // The value 0
+		copy(data, []byte{0x30})
+		return 1, nil
+	}
+
+	bz, err := i.Marshal()
+	if err != nil {
+		return 0, err
+	}
+
+	copy(data, bz)
+	return len(bz), nil
+}
+
+// Unmarshal implements the gogo proto custom type interface.
+func (i *BigInt) Unmarshal(data []byte) error {
+	if len(data) == 0 {
+		i = nil
+		return nil
+	}
+
+	if i.i == nil {
+		i.i = new(big.Int)
+	}
+
+	if err := i.i.UnmarshalText(data); err != nil {
+		return err
+	}
+
+	if i.i.BitLen() > maxBitLen {
+		return fmt.Errorf("integer out of range; got: %d, max: %d", i.i.BitLen(), maxBitLen)
+	}
+
+	return nil
+}
+
+// Size implements the gogo proto custom type interface.
+func (i *BigInt) Size() int {
+	bz, _ := i.Marshal()
+	return len(bz)
+}
+
+// Override Amino binary serialization by proxying to protobuf.
+func (i BigInt) MarshalAmino() ([]byte, error)   { return i.Marshal() }
+func (i *BigInt) UnmarshalAmino(bz []byte) error { return i.Unmarshal(bz) }
+
+// intended to be used with require/assert:  require.True(IntEq(...))
+func IntEq(t *testing.T, exp, got BigInt) (*testing.T, bool, string, string, string) {
+	return t, exp.Equal(got), "expected:\t%v\ngot:\t\t%v", exp.String(), got.String()
+}


Test patch:
diff --git a/osmomath/decimal_test.go b/osmomath/decimal_test.go
new file mode 100644
index 000000000..6456e385e
--- /dev/null
+++ b/osmomath/decimal_test.go
@@ -0,0 +1,563 @@
+package osmomath
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"math/big"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	"github.com/stretchr/testify/suite"
+	"gopkg.in/yaml.v2"
+)
+
+type decimalTestSuite struct {
+	suite.Suite
+}
+
+func TestDecimalTestSuite(t *testing.T) {
+	suite.Run(t, new(decimalTestSuite))
+}
+
+func TestDecApproxEq(t *testing.T) {
+	// d1 = 0.55, d2 = 0.6, tol = 0.1
+	d1 := NewDecWithPrec(55, 2)
+	d2 := NewDecWithPrec(6, 1)
+	tol := NewDecWithPrec(1, 1)
+
+	require.True(DecApproxEq(t, d1, d2, tol))
+
+	// d1 = 0.55, d2 = 0.6, tol = 1E-5
+	d1 = NewDecWithPrec(55, 2)
+	d2 = NewDecWithPrec(6, 1)
+	tol = NewDecWithPrec(1, 5)
+
+	require.False(DecApproxEq(t, d1, d2, tol))
+
+	// d1 = 0.6, d2 = 0.61, tol = 0.01
+	d1 = NewDecWithPrec(6, 1)
+	d2 = NewDecWithPrec(61, 2)
+	tol = NewDecWithPrec(1, 2)
+
+	require.True(DecApproxEq(t, d1, d2, tol))
+}
+
+// create a decimal from a decimal string (ex. "1234.5678")
+func (s *decimalTestSuite) mustNewDecFromStr(str string) (d BigDec) {
+	d, err := NewDecFromStr(str)
+	s.Require().NoError(err)
+
+	return d
+}
+
+func (s *decimalTestSuite) TestNewDecFromStr() {
+	largeBigInt, success := new(big.Int).SetString("3144605511029693144278234343371835", 10)
+	s.Require().True(success)
+
+	tests := []struct {
+		decimalStr string
+		expErr     bool
+		exp        BigDec
+	}{
+		{"", true, BigDec{}},
+		{"0.-75", true, BigDec{}},
+		{"0", false, NewBigDec(0)},
+		{"1", false, NewBigDec(1)},
+		{"1.1", false, NewDecWithPrec(11, 1)},
+		{"0.75", false, NewDecWithPrec(75, 2)},
+		{"0.8", false, NewDecWithPrec(8, 1)},
+		{"0.11111", false, NewDecWithPrec(11111, 5)},
+		{"314460551102969.3144278234343371835", true, NewBigDec(3141203149163817869)},
+		{"314460551102969314427823434337.1835718092488231350",
+			true, NewDecFromBigIntWithPrec(largeBigInt, 4)},
+		{"314460551102969314427823434337.1835",
+			false, NewDecFromBigIntWithPrec(largeBigInt, 4)},
+		{".", true, BigDec{}},
+		{".0", true, NewBigDec(0)},
+		{"1.", true, NewBigDec(1)},
+		{"foobar", true, BigDec{}},
+		{"0.foobar", true, BigDec{}},
+		{"0.foobar.", true, BigDec{}},
+		{"23258839177459420497578361852416145099316523541994177929007686373780457219628733546438113622840434097944400691400517693873107252115668992", true, BigDec{}},
+	}
+
+	for tcIndex, tc := range tests {
+		res, err := NewDecFromStr(tc.decimalStr)
+		if tc.expErr {
+			s.Require().NotNil(err, "error expected, decimalStr %v, tc %v", tc.decimalStr, tcIndex)
+		} else {
+			s.Require().Nil(err, "unexpected error, decimalStr %v, tc %v", tc.decimalStr, tcIndex)
+			s.Require().True(res.Equal(tc.exp), "equality was incorrect, res %v, exp %v, tc %v", res, tc.exp, tcIndex)
+		}
+
+		// negative tc
+		res, err = NewDecFromStr("-" + tc.decimalStr)
+		if tc.expErr {
+			s.Require().NotNil(err, "error expected, decimalStr %v, tc %v", tc.decimalStr, tcIndex)
+		} else {
+			s.Require().Nil(err, "unexpected error, decimalStr %v, tc %v", tc.decimalStr, tcIndex)
+			exp := tc.exp.Mul(NewBigDec(-1))
+			s.Require().True(res.Equal(exp), "equality was incorrect, res %v, exp %v, tc %v", res, exp, tcIndex)
+		}
+	}
+}
+
+func (s *decimalTestSuite) TestDecString() {
+	tests := []struct {
+		d    BigDec
+		want string
+	}{
+		{NewBigDec(0), "0.000000000000000000"},
+		{NewBigDec(1), "1.000000000000000000"},
+		{NewBigDec(10), "10.000000000000000000"},
+		{NewBigDec(12340), "12340.000000000000000000"},
+		{NewDecWithPrec(12340, 4), "1.234000000000000000"},
+		{NewDecWithPrec(12340, 5), "0.123400000000000000"},
+		{NewDecWithPrec(12340, 8), "0.000123400000000000"},
+		{NewDecWithPrec(1009009009009009009, 17), "10.090090090090090090"},
+	}
+	for tcIndex, tc := range tests {
+		s.Require().Equal(tc.want, tc.d.String(), "bad String(), index: %v", tcIndex)
+	}
+}
+
+func (s *decimalTestSuite) TestDecFloat64() {
+	tests := []struct {
+		d    BigDec
+		want float64
+	}{
+		{NewBigDec(0), 0.000000000000000000},
+		{NewBigDec(1), 1.000000000000000000},
+		{NewBigDec(10), 10.000000000000000000},
+		{NewBigDec(12340), 12340.000000000000000000},
+		{NewDecWithPrec(12340, 4), 1.234000000000000000},
+		{NewDecWithPrec(12340, 5), 0.123400000000000000},
+		{NewDecWithPrec(12340, 8), 0.000123400000000000},
+		{NewDecWithPrec(1009009009009009009, 17), 10.090090090090090090},
+	}
+	for tcIndex, tc := range tests {
+		value, err := tc.d.Float64()
+		s.Require().Nil(err, "error getting Float64(), index: %v", tcIndex)
+		s.Require().Equal(tc.want, value, "bad Float64(), index: %v", tcIndex)
+		s.Require().Equal(tc.want, tc.d.MustFloat64(), "bad MustFloat64(), index: %v", tcIndex)
+	}
+}
+
+func (s *decimalTestSuite) TestEqualities() {
+	tests := []struct {
+		d1, d2     BigDec
+		gt, lt, eq bool
+	}{
+		{NewBigDec(0), NewBigDec(0), false, false, true},
+		{NewDecWithPrec(0, 2), NewDecWithPrec(0, 4), false, false, true},
+		{NewDecWithPrec(100, 0), NewDecWithPrec(100, 0), false, false, true},
+		{NewDecWithPrec(-100, 0), NewDecWithPrec(-100, 0), false, false, true},
+		{NewDecWithPrec(-1, 1), NewDecWithPrec(-1, 1), false, false, true},
+		{NewDecWithPrec(3333, 3), NewDecWithPrec(3333, 3), false, false, true},
+
+		{NewDecWithPrec(0, 0), NewDecWithPrec(3333, 3), false, true, false},
+		{NewDecWithPrec(0, 0), NewDecWithPrec(100, 0), false, true, false},
+		{NewDecWithPrec(-1, 0), NewDecWithPrec(3333, 3), false, true, false},
+		{NewDecWithPrec(-1, 0), NewDecWithPrec(100, 0), false, true, false},
+		{NewDecWithPrec(1111, 3), NewDecWithPrec(100, 0), false, true, false},
+		{NewDecWithPrec(1111, 3), NewDecWithPrec(3333, 3), false, true, false},
+		{NewDecWithPrec(-3333, 3), NewDecWithPrec(-1111, 3), false, true, false},
+
+		{NewDecWithPrec(3333, 3), NewDecWithPrec(0, 0), true, false, false},
+		{NewDecWithPrec(100, 0), NewDecWithPrec(0, 0), true, false, false},
+		{NewDecWithPrec(3333, 3), NewDecWithPrec(-1, 0), true, false, false},
+		{NewDecWithPrec(100, 0), NewDecWithPrec(-1, 0), true, false, false},
+		{NewDecWithPrec(100, 0), NewDecWithPrec(1111, 3), true, false, false},
+		{NewDecWithPrec(3333, 3), NewDecWithPrec(1111, 3), true, false, false},
+		{NewDecWithPrec(-1111, 3), NewDecWithPrec(-3333, 3), true, false, false},
+	}
+
+	for tcIndex, tc := range tests {
+		s.Require().Equal(tc.gt, tc.d1.GT(tc.d2), "GT result is incorrect, tc %d", tcIndex)
+		s.Require().Equal(tc.lt, tc.d1.LT(tc.d2), "LT result is incorrect, tc %d", tcIndex)
+		s.Require().Equal(tc.eq, tc.d1.Equal(tc.d2), "equality result is incorrect, tc %d", tcIndex)
+	}
+
+}
+
+func (s *decimalTestSuite) TestDecsEqual() {
+	tests := []struct {
+		d1s, d2s []BigDec
+		eq       bool
+	}{
+		{[]BigDec{NewBigDec(0)}, []BigDec{NewBigDec(0)}, true},
+		{[]BigDec{NewBigDec(0)}, []BigDec{NewBigDec(1)}, false},
+		{[]BigDec{NewBigDec(0)}, []BigDec{}, false},
+		{[]BigDec{NewBigDec(0), NewBigDec(1)}, []BigDec{NewBigDec(0), NewBigDec(1)}, true},
+		{[]BigDec{NewBigDec(1), NewBigDec(0)}, []BigDec{NewBigDec(1), NewBigDec(0)}, true},
+		{[]BigDec{NewBigDec(1), NewBigDec(0)}, []BigDec{NewBigDec(0), NewBigDec(1)}, false},
+		{[]BigDec{NewBigDec(1), NewBigDec(0)}, []BigDec{NewBigDec(1)}, false},
+		{[]BigDec{NewBigDec(1), NewBigDec(2)}, []BigDec{NewBigDec(2), NewBigDec(4)}, false},
+		{[]BigDec{NewBigDec(3), NewBigDec(18)}, []BigDec{NewBigDec(1), NewBigDec(6)}, false},
+	}
+
+	for tcIndex, tc := range tests {
+		s.Require().Equal(tc.eq, DecsEqual(tc.d1s, tc.d2s), "equality of decional arrays is incorrect, tc %d", tcIndex)
+		s.Require().Equal(tc.eq, DecsEqual(tc.d2s, tc.d1s), "equality of decional arrays is incorrect (converse), tc %d", tcIndex)
+	}
+}
+
+func (s *decimalTestSuite) TestArithmetic() {
+	tests := []struct {
+		d1, d2                                BigDec
+		expMul, expMulTruncate                BigDec
+		expQuo, expQuoRoundUp, expQuoTruncate BigDec
+		expAdd, expSub                        BigDec
+	}{
+		//  d1         d2         MUL    MulTruncate    QUO    QUORoundUp QUOTrunctate  ADD         SUB
+		{NewBigDec(0), NewBigDec(0), NewBigDec(0), NewBigDec(0), NewBigDec(0), NewBigDec(0), NewBigDec(0), NewBigDec(0), NewBigDec(0)},
+		{NewBigDec(1), NewBigDec(0), NewBigDec(0), NewBigDec(0), NewBigDec(0), NewBigDec(0), NewBigDec(0), NewBigDec(1), NewBigDec(1)},
+		{NewBigDec(0), NewBigDec(1), NewBigDec(0), NewBigDec(0), NewBigDec(0), NewBigDec(0), NewBigDec(0), NewBigDec(1), NewBigDec(-1)},
+		{NewBigDec(0), NewBigDec(-1), NewBigDec(0), NewBigDec(0), NewBigDec(0), NewBigDec(0), NewBigDec(0), NewBigDec(-1), NewBigDec(1)},
+		{NewBigDec(-1), NewBigDec(0), NewBigDec(0), NewBigDec(0), NewBigDec(0), NewBigDec(0), NewBigDec(0), NewBigDec(-1), NewBigDec(-1)},
+
+		{NewBigDec(1), NewBigDec(1), NewBigDec(1), NewBigDec(1), NewBigDec(1), NewBigDec(1), NewBigDec(1), NewBigDec(2), NewBigDec(0)},
+		{NewBigDec(-1), NewBigDec(-1), NewBigDec(1), NewBigDec(1), NewBigDec(1), NewBigDec(1), NewBigDec(1), NewBigDec(-2), NewBigDec(0)},
+		{NewBigDec(1), NewBigDec(-1), NewBigDec(-1), NewBigDec(-1), NewBigDec(-1), NewBigDec(-1), NewBigDec(-1), NewBigDec(0), NewBigDec(2)},
+		{NewBigDec(-1), NewBigDec(1), NewBigDec(-1), NewBigDec(-1), NewBigDec(-1), NewBigDec(-1), NewBigDec(-1), NewBigDec(0), NewBigDec(-2)},
+
+		{NewBigDec(3), NewBigDec(7), NewBigDec(21), NewBigDec(21),
+			NewDecWithPrec(428571428571428571, 18), NewDecWithPrec(428571428571428572, 18), NewDecWithPrec(428571428571428571, 18),
+			NewBigDec(10), NewBigDec(-4)},
+		{NewBigDec(2), NewBigDec(4), NewBigDec(8), NewBigDec(8), NewDecWithPrec(5, 1), NewDecWithPrec(5, 1), NewDecWithPrec(5, 1),
+			NewBigDec(6), NewBigDec(-2)},
+
+		{NewBigDec(100), NewBigDec(100), NewBigDec(10000), NewBigDec(10000), NewBigDec(1), NewBigDec(1), NewBigDec(1), NewBigDec(200), NewBigDec(0)},
+
+		{NewDecWithPrec(15, 1), NewDecWithPrec(15, 1), NewDecWithPrec(225, 2), NewDecWithPrec(225, 2),
+			NewBigDec(1), NewBigDec(1), NewBigDec(1), NewBigDec(3), NewBigDec(0)},
+		{NewDecWithPrec(3333, 4), NewDecWithPrec(333, 4), NewDecWithPrec(1109889, 8), NewDecWithPrec(1109889, 8),
+			MustNewDecFromStr("10.009009009009009009"), MustNewDecFromStr("10.009009009009009010"), MustNewDecFromStr("10.009009009009009009"),
+			NewDecWithPrec(3666, 4), NewDecWithPrec(3, 1)},
+	}
+
+	for tcIndex, tc := range tests {
+		tc := tc
+		resAdd := tc.d1.Add(tc.d2)
+		resSub := tc.d1.Sub(tc.d2)
+		resMul := tc.d1.Mul(tc.d2)
+		resMulTruncate := tc.d1.MulTruncate(tc.d2)
+		s.Require().True(tc.expAdd.Equal(resAdd), "exp %v, res %v, tc %d", tc.expAdd, resAdd, tcIndex)
+		s.Require().True(tc.expSub.Equal(resSub), "exp %v, res %v, tc %d", tc.expSub, resSub, tcIndex)
+		s.Require().True(tc.expMul.Equal(resMul), "exp %v, res %v, tc %d", tc.expMul, resMul, tcIndex)
+		s.Require().True(tc.expMulTruncate.Equal(resMulTruncate), "exp %v, res %v, tc %d", tc.expMulTruncate, resMulTruncate, tcIndex)
+
+		if tc.d2.IsZero() { // panic for divide by zero
+			s.Require().Panics(func() { tc.d1.Quo(tc.d2) })
+		} else {
+			resQuo := tc.d1.Quo(tc.d2)
+			s.Require().True(tc.expQuo.Equal(resQuo), "exp %v, res %v, tc %d", tc.expQuo.String(), resQuo.String(), tcIndex)
+
+			resQuoRoundUp := tc.d1.QuoRoundUp(tc.d2)
+			s.Require().True(tc.expQuoRoundUp.Equal(resQuoRoundUp), "exp %v, res %v, tc %d",
+				tc.expQuoRoundUp.String(), resQuoRoundUp.String(), tcIndex)
+
+			resQuoTruncate := tc.d1.QuoTruncate(tc.d2)
+			s.Require().True(tc.expQuoTruncate.Equal(resQuoTruncate), "exp %v, res %v, tc %d",
+				tc.expQuoTruncate.String(), resQuoTruncate.String(), tcIndex)
+		}
+	}
+}
+
+func (s *decimalTestSuite) TestBankerRoundChop() {
+	tests := []struct {
+		d1  BigDec
+		exp int64
+	}{
+		{s.mustNewDecFromStr("0.25"), 0},
+		{s.mustNewDecFromStr("0"), 0},
+		{s.mustNewDecFromStr("1"), 1},
+		{s.mustNewDecFromStr("0.75"), 1},
+		{s.mustNewDecFromStr("0.5"), 0},
+		{s.mustNewDecFromStr("7.5"), 8},
+		{s.mustNewDecFromStr("1.5"), 2},
+		{s.mustNewDecFromStr("2.5"), 2},
+		{s.mustNewDecFromStr("0.545"), 1}, // 0.545-> 1 even though 5 is first decimal and 1 not even
+		{s.mustNewDecFromStr("1.545"), 2},
+	}
+
+	for tcIndex, tc := range tests {
+		resNeg := tc.d1.Neg().RoundInt64()
+		s.Require().Equal(-1*tc.exp, resNeg, "negative tc %d", tcIndex)
+
+		resPos := tc.d1.RoundInt64()
+		s.Require().Equal(tc.exp, resPos, "positive tc %d", tcIndex)
+	}
+}
+
+func (s *decimalTestSuite) TestTruncate() {
+	tests := []struct {
+		d1  BigDec
+		exp int64
+	}{
+		{s.mustNewDecFromStr("0"), 0},
+		{s.mustNewDecFromStr("0.25"), 0},
+		{s.mustNewDecFromStr("0.75"), 0},
+		{s.mustNewDecFromStr("1"), 1},
+		{s.mustNewDecFromStr("1.5"), 1},
+		{s.mustNewDecFromStr("7.5"), 7},
+		{s.mustNewDecFromStr("7.6"), 7},
+		{s.mustNewDecFromStr("7.4"), 7},
+		{s.mustNewDecFromStr("100.1"), 100},
+		{s.mustNewDecFromStr("1000.1"), 1000},
+	}
+
+	for tcIndex, tc := range tests {
+		resNeg := tc.d1.Neg().TruncateInt64()
+		s.Require().Equal(-1*tc.exp, resNeg, "negative tc %d", tcIndex)
+
+		resPos := tc.d1.TruncateInt64()
+		s.Require().Equal(tc.exp, resPos, "positive tc %d", tcIndex)
+	}
+}
+
+func (s *decimalTestSuite) TestStringOverflow() {
+	// two random 64 bit primes
+	dec1, err := NewDecFromStr("51643150036226787134389711697696177267")
+	s.Require().NoError(err)
+	dec2, err := NewDecFromStr("-31798496660535729618459429845579852627")
+	s.Require().NoError(err)
+	dec3 := dec1.Add(dec2)
+	s.Require().Equal(
+		"19844653375691057515930281852116324640.000000000000000000",
+		dec3.String(),
+	)
+}
+
+func (s *decimalTestSuite) TestDecMulInt() {
+	tests := []struct {
+		sdkDec BigDec
+		sdkInt BigInt
+		want   BigDec
+	}{
+		{NewBigDec(10), NewInt(2), NewBigDec(20)},
+		{NewBigDec(1000000), NewInt(100), NewBigDec(100000000)},
+		{NewDecWithPrec(1, 1), NewInt(10), NewBigDec(1)},
+		{NewDecWithPrec(1, 5), NewInt(20), NewDecWithPrec(2, 4)},
+	}
+	for i, tc := range tests {
+		got := tc.sdkDec.MulInt(tc.sdkInt)
+		s.Require().Equal(tc.want, got, "Incorrect result on test case %d", i)
+	}
+}
+
+func (s *decimalTestSuite) TestDecCeil() {
+	testCases := []struct {
+		input    BigDec
+		expected BigDec
+	}{
+		{NewDecWithPrec(1000000000000000, Precision), NewBigDec(1)}, // 0.001 => 1.0
+		{NewDecWithPrec(-1000000000000000, Precision), ZeroDec()},   // -0.001 => 0.0
+		{ZeroDec(), ZeroDec()}, // 0.0 => 0.0
+		{NewDecWithPrec(900000000000000000, Precision), NewBigDec(1)},    // 0.9 => 1.0
+		{NewDecWithPrec(4001000000000000000, Precision), NewBigDec(5)},   // 4.001 => 5.0
+		{NewDecWithPrec(-4001000000000000000, Precision), NewBigDec(-4)}, // -4.001 => -4.0
+		{NewDecWithPrec(4700000000000000000, Precision), NewBigDec(5)},   // 4.7 => 5.0
+		{NewDecWithPrec(-4700000000000000000, Precision), NewBigDec(-4)}, // -4.7 => -4.0
+	}
+
+	for i, tc := range testCases {
+		res := tc.input.Ceil()
+		s.Require().Equal(tc.expected, res, "unexpected result for test case %d, input: %v", i, tc.input)
+	}
+}
+
+func (s *decimalTestSuite) TestPower() {
+	testCases := []struct {
+		input    BigDec
+		power    uint64
+		expected BigDec
+	}{
+		{OneDec(), 10, OneDec()},                                               // 1.0 ^ (10) => 1.0
+		{NewDecWithPrec(5, 1), 2, NewDecWithPrec(25, 2)},                       // 0.5 ^ 2 => 0.25
+		{NewDecWithPrec(2, 1), 2, NewDecWithPrec(4, 2)},                        // 0.2 ^ 2 => 0.04
+		{NewDecFromInt(NewInt(3)), 3, NewDecFromInt(NewInt(27))},               // 3 ^ 3 => 27
+		{NewDecFromInt(NewInt(-3)), 4, NewDecFromInt(NewInt(81))},              // -3 ^ 4 = 81
+		{NewDecWithPrec(1414213562373095049, 18), 2, NewDecFromInt(NewInt(2))}, // 1.414213562373095049 ^ 2 = 2
+	}
+
+	for i, tc := range testCases {
+		res := tc.input.Power(tc.power)
+		s.Require().True(tc.expected.Sub(res).Abs().LTE(SmallestDec()), "unexpected result for test case %d, input: %v", i, tc.input)
+	}
+}
+
+func (s *decimalTestSuite) TestApproxRoot() {
+	testCases := []struct {
+		input    BigDec
+		root     uint64
+		expected BigDec
+	}{
+		{OneDec(), 10, OneDec()},                                                       // 1.0 ^ (0.1) => 1.0
+		{NewDecWithPrec(25, 2), 2, NewDecWithPrec(5, 1)},                               // 0.25 ^ (0.5) => 0.5
+		{NewDecWithPrec(4, 2), 2, NewDecWithPrec(2, 1)},                                // 0.04 ^ (0.5) => 0.2
+		{NewDecFromInt(NewInt(27)), 3, NewDecFromInt(NewInt(3))},                       // 27 ^ (1/3) => 3
+		{NewDecFromInt(NewInt(-81)), 4, NewDecFromInt(NewInt(-3))},                     // -81 ^ (0.25) => -3
+		{NewDecFromInt(NewInt(2)), 2, NewDecWithPrec(1414213562373095049, 18)},         // 2 ^ (0.5) => 1.414213562373095049
+		{NewDecWithPrec(1005, 3), 31536000, MustNewDecFromStr("1.000000000158153904")}, // 1.005 ^ (1/31536000) ≈ 1.00000000016
+		{SmallestDec(), 2, NewDecWithPrec(1, 9)},                                       // 1e-18 ^ (0.5) => 1e-9
+		{SmallestDec(), 3, MustNewDecFromStr("0.000000999999999997")},                  // 1e-18 ^ (1/3) => 1e-6
+		{NewDecWithPrec(1, 8), 3, MustNewDecFromStr("0.002154434690031900")},           // 1e-8 ^ (1/3) ≈ 0.00215443469
+	}
+
+	// In the case of 1e-8 ^ (1/3), the result repeats every 5 iterations starting from iteration 24
+	// (i.e. 24, 29, 34, ... give the same result) and never converges enough. The maximum number of
+	// iterations (100) causes the result at iteration 100 to be returned, regardless of convergence.
+
+	for i, tc := range testCases {
+		res, err := tc.input.ApproxRoot(tc.root)
+		s.Require().NoError(err)
+		s.Require().True(tc.expected.Sub(res).Abs().LTE(SmallestDec()), "unexpected result for test case %d, input: %v", i, tc.input)
+	}
+}
+
+func (s *decimalTestSuite) TestApproxSqrt() {
+	testCases := []struct {
+		input    BigDec
+		expected BigDec
+	}{
+		{OneDec(), OneDec()},                                                // 1.0 => 1.0
+		{NewDecWithPrec(25, 2), NewDecWithPrec(5, 1)},                       // 0.25 => 0.5
+		{NewDecWithPrec(4, 2), NewDecWithPrec(2, 1)},                        // 0.09 => 0.3
+		{NewDecFromInt(NewInt(9)), NewDecFromInt(NewInt(3))},                // 9 => 3
+		{NewDecFromInt(NewInt(-9)), NewDecFromInt(NewInt(-3))},              // -9 => -3
+		{NewDecFromInt(NewInt(2)), NewDecWithPrec(1414213562373095049, 18)}, // 2 => 1.414213562373095049
+	}
+
+	for i, tc := range testCases {
+		res, err := tc.input.ApproxSqrt()
+		s.Require().NoError(err)
+		s.Require().Equal(tc.expected, res, "unexpected result for test case %d, input: %v", i, tc.input)
+	}
+}
+
+func (s *decimalTestSuite) TestDecSortableBytes() {
+	tests := []struct {
+		d    BigDec
+		want []byte
+	}{
+		{NewBigDec(0), []byte("000000000000000000.000000000000000000")},
+		{NewBigDec(1), []byte("000000000000000001.000000000000000000")},
+		{NewBigDec(10), []byte("000000000000000010.000000000000000000")},
+		{NewBigDec(12340), []byte("000000000000012340.000000000000000000")},
+		{NewDecWithPrec(12340, 4), []byte("000000000000000001.234000000000000000")},
+		{NewDecWithPrec(12340, 5), []byte("000000000000000000.123400000000000000")},
+		{NewDecWithPrec(12340, 8), []byte("000000000000000000.000123400000000000")},
+		{NewDecWithPrec(1009009009009009009, 17), []byte("000000000000000010.090090090090090090")},
+		{NewDecWithPrec(-1009009009009009009, 17), []byte("-000000000000000010.090090090090090090")},
+		{NewBigDec(1000000000000000000), []byte("max")},
+		{NewBigDec(-1000000000000000000), []byte("--")},
+	}
+	for tcIndex, tc := range tests {
+		s.Require().Equal(tc.want, SortableDecBytes(tc.d), "bad String(), index: %v", tcIndex)
+	}
+
+	s.Require().Panics(func() { SortableDecBytes(NewBigDec(1000000000000000001)) })
+	s.Require().Panics(func() { SortableDecBytes(NewBigDec(-1000000000000000001)) })
+}
+
+func (s *decimalTestSuite) TestDecEncoding() {
+	testCases := []struct {
+		input   BigDec
+		rawBz   string
+		jsonStr string
+		yamlStr string
+	}{
+		{
+			NewBigDec(0), "30",
+			"\"0.000000000000000000\"",
+			"\"0.000000000000000000\"\n",
+		},
+		{
+			NewDecWithPrec(4, 2),
+			"3430303030303030303030303030303030",
+			"\"0.040000000000000000\"",
+			"\"0.040000000000000000\"\n",
+		},
+		{
+			NewDecWithPrec(-4, 2),
+			"2D3430303030303030303030303030303030",
+			"\"-0.040000000000000000\"",
+			"\"-0.040000000000000000\"\n",
+		},
+		{
+			NewDecWithPrec(1414213562373095049, 18),
+			"31343134323133353632333733303935303439",
+			"\"1.414213562373095049\"",
+			"\"1.414213562373095049\"\n",
+		},
+		{
+			NewDecWithPrec(-1414213562373095049, 18),
+			"2D31343134323133353632333733303935303439",
+			"\"-1.414213562373095049\"",
+			"\"-1.414213562373095049\"\n",
+		},
+	}
+
+	for _, tc := range testCases {
+		bz, err := tc.input.Marshal()
+		s.Require().NoError(err)
+		s.Require().Equal(tc.rawBz, fmt.Sprintf("%X", bz))
+
+		var other BigDec
+		s.Require().NoError((&other).Unmarshal(bz))
+		s.Require().True(tc.input.Equal(other))
+
+		bz, err = json.Marshal(tc.input)
+		s.Require().NoError(err)
+		s.Require().Equal(tc.jsonStr, string(bz))
+		s.Require().NoError(json.Unmarshal(bz, &other))
+		s.Require().True(tc.input.Equal(other))
+
+		bz, err = yaml.Marshal(tc.input)
+		s.Require().NoError(err)
+		s.Require().Equal(tc.yamlStr, string(bz))
+	}
+}
+
+// Showcase that different orders of operations causes different results.
+func (s *decimalTestSuite) TestOperationOrders() {
+	n1 := NewBigDec(10)
+	n2 := NewBigDec(1000000010)
+	s.Require().Equal(n1.Mul(n2).Quo(n2), NewBigDec(10))
+	s.Require().NotEqual(n1.Mul(n2).Quo(n2), n1.Quo(n2).Mul(n2))
+}
+
+func BenchmarkMarshalTo(b *testing.B) {
+	b.ReportAllocs()
+	bis := []struct {
+		in   BigDec
+		want []byte
+	}{
+		{
+			NewBigDec(1e8), []byte{
+				0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+				0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+				0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+			},
+		},
+		{NewBigDec(0), []byte{0x30}},
+	}
+	data := make([]byte, 100)
+
+	b.ReportAllocs()
+	b.ResetTimer()
+
+	for i := 0; i < b.N; i++ {
+		for _, bi := range bis {
+			if n, err := bi.in.MarshalTo(data); err != nil {
+				b.Fatal(err)
+			} else {
+				if !bytes.Equal(data[:n], bi.want) {
+					b.Fatalf("Mismatch\nGot:  % x\nWant: % x\n", data[:n], bi.want)
+				}
+			}
+		}
+	}
+}
diff --git a/osmomath/int_test.go b/osmomath/int_test.go
new file mode 100644
index 000000000..a770301c0
--- /dev/null
+++ b/osmomath/int_test.go
@@ -0,0 +1,472 @@
+package osmomath
+
+import (
+	"fmt"
+	"math/big"
+	"math/rand"
+	"strconv"
+	"testing"
+
+	"github.com/stretchr/testify/suite"
+
+	sdk "github.com/cosmos/cosmos-sdk/types"
+)
+
+type intTestSuite struct {
+	suite.Suite
+}
+
+func TestIntTestSuite(t *testing.T) {
+	suite.Run(t, new(intTestSuite))
+}
+
+func (s *intTestSuite) SetupSuite() {
+	s.T().Parallel()
+}
+
+func (s *intTestSuite) TestFromInt64() {
+	for n := 0; n < 20; n++ {
+		r := rand.Int63()
+		s.Require().Equal(r, NewInt(r).Int64())
+	}
+}
+
+func (s *intTestSuite) TestFromUint64() {
+	for n := 0; n < 20; n++ {
+		r := rand.Uint64()
+		s.Require().True(NewIntFromUint64(r).IsUint64())
+		s.Require().Equal(r, NewIntFromUint64(r).Uint64())
+	}
+}
+
+func (s *intTestSuite) TestIntPanic() {
+	// Max Int = 2^512-1 = 1.340781e+154
+	// Min Int = -(2^512-1) = -1.340781e+154
+	s.Require().NotPanics(func() { NewIntWithDecimal(4, 153) })
+	i1 := NewIntWithDecimal(5, 153)
+	s.Require().NotPanics(func() { NewIntWithDecimal(5, 153) })
+	i2 := NewIntWithDecimal(6, 153)
+	s.Require().NotPanics(func() { NewIntWithDecimal(6, 153) })
+	i3 := NewIntWithDecimal(7, 153)
+
+	s.Require().Panics(func() { NewIntWithDecimal(2, 154) })
+	s.Require().Panics(func() { NewIntWithDecimal(9, 160) })
+
+	// Overflow check
+	s.Require().NotPanics(func() { i1.Add(i1) })
+	s.Require().NotPanics(func() { i2.Add(i2) })
+	s.Require().Panics(func() { i3.Add(i3) })
+
+	s.Require().NotPanics(func() { i1.Sub(i1.Neg()) })
+	s.Require().NotPanics(func() { i2.Sub(i2.Neg()) })
+	s.Require().Panics(func() { i3.Sub(i3.Neg()) })
+
+	s.Require().Panics(func() { i1.Mul(i1) })
+	s.Require().Panics(func() { i2.Mul(i2) })
+	s.Require().Panics(func() { i3.Mul(i3) })
+
+	s.Require().Panics(func() { i1.Neg().Mul(i1.Neg()) })
+	s.Require().Panics(func() { i2.Neg().Mul(i2.Neg()) })
+	s.Require().Panics(func() { i3.Neg().Mul(i3.Neg()) })
+
+	// Underflow check
+	i3n := i3.Neg()
+	s.Require().NotPanics(func() { i3n.Sub(i1) })
+	s.Require().NotPanics(func() { i3n.Sub(i2) })
+	s.Require().Panics(func() { i3n.Sub(i3) })
+
+	s.Require().NotPanics(func() { i3n.Add(i1.Neg()) })
+	s.Require().NotPanics(func() { i3n.Add(i2.Neg()) })
+	s.Require().Panics(func() { i3n.Add(i3.Neg()) })
+
+	s.Require().Panics(func() { i1.Mul(i1.Neg()) })
+	s.Require().Panics(func() { i2.Mul(i2.Neg()) })
+	s.Require().Panics(func() { i3.Mul(i3.Neg()) })
+
+	// Bound check
+	intmax := NewIntFromBigInt(new(big.Int).Sub(new(big.Int).Exp(big.NewInt(2), big.NewInt(512), nil), big.NewInt(1)))
+	intmin := intmax.Neg()
+	s.Require().NotPanics(func() { intmax.Add(ZeroInt()) })
+	s.Require().NotPanics(func() { intmin.Sub(ZeroInt()) })
+	s.Require().Panics(func() { intmax.Add(OneInt()) })
+	s.Require().Panics(func() { intmin.Sub(OneInt()) })
+
+	s.Require().NotPanics(func() { NewIntFromBigInt(nil) })
+	s.Require().True(NewIntFromBigInt(nil).IsNil())
+
+	// Division-by-zero check
+	s.Require().Panics(func() { i1.Quo(NewInt(0)) })
+
+	s.Require().NotPanics(func() { BigInt{}.BigInt() })
+}
+
+// Tests below uses randomness
+// Since we are using *big.Int as underlying value
+// and (U/)Int is immutable value(see TestImmutability(U/)Int)
+// it is safe to use randomness in the tests
+func (s *intTestSuite) TestIdentInt() {
+	for d := 0; d < 1000; d++ {
+		n := rand.Int63()
+		i := NewInt(n)
+
+		ifromstr, ok := NewIntFromString(strconv.FormatInt(n, 10))
+		s.Require().True(ok)
+
+		cases := []int64{
+			i.Int64(),
+			i.BigInt().Int64(),
+			ifromstr.Int64(),
+			NewIntFromBigInt(big.NewInt(n)).Int64(),
+			NewIntWithDecimal(n, 0).Int64(),
+		}
+
+		for tcnum, tc := range cases {
+			s.Require().Equal(n, tc, "Int is modified during conversion. tc #%d", tcnum)
+		}
+	}
+}
+
+func minint(i1, i2 int64) int64 {
+	if i1 < i2 {
+		return i1
+	}
+	return i2
+}
+
+func maxint(i1, i2 int64) int64 {
+	if i1 > i2 {
+		return i1
+	}
+	return i2
+}
+
+func (s *intTestSuite) TestArithInt() {
+	for d := 0; d < 1000; d++ {
+		n1 := int64(rand.Int31())
+		i1 := NewInt(n1)
+		n2 := int64(rand.Int31())
+		i2 := NewInt(n2)
+
+		cases := []struct {
+			ires BigInt
+			nres int64
+		}{
+			{i1.Add(i2), n1 + n2},
+			{i1.Sub(i2), n1 - n2},
+			{i1.Mul(i2), n1 * n2},
+			{i1.Quo(i2), n1 / n2},
+			{i1.AddRaw(n2), n1 + n2},
+			{i1.SubRaw(n2), n1 - n2},
+			{i1.MulRaw(n2), n1 * n2},
+			{i1.QuoRaw(n2), n1 / n2},
+			{MinInt(i1, i2), minint(n1, n2)},
+			{MaxInt(i1, i2), maxint(n1, n2)},
+			{i1.Neg(), -n1},
+			{i1.Abs(), n1},
+			{i1.Neg().Abs(), n1},
+		}
+
+		for tcnum, tc := range cases {
+			s.Require().Equal(tc.nres, tc.ires.Int64(), "Int arithmetic operation does not match with int64 operation. tc #%d", tcnum)
+		}
+	}
+
+}
+
+func (s *intTestSuite) TestCompInt() {
+	for d := 0; d < 1000; d++ {
+		n1 := int64(rand.Int31())
+		i1 := NewInt(n1)
+		n2 := int64(rand.Int31())
+		i2 := NewInt(n2)
+
+		cases := []struct {
+			ires bool
+			nres bool
+		}{
+			{i1.Equal(i2), n1 == n2},
+			{i1.GT(i2), n1 > n2},
+			{i1.LT(i2), n1 < n2},
+			{i1.LTE(i2), n1 <= n2},
+		}
+
+		for tcnum, tc := range cases {
+			s.Require().Equal(tc.nres, tc.ires, "Int comparison operation does not match with int64 operation. tc #%d", tcnum)
+		}
+	}
+}
+
+func randint() BigInt {
+	return NewInt(rand.Int63())
+}
+
+func (s *intTestSuite) TestImmutabilityAllInt() {
+	ops := []func(*BigInt){
+		func(i *BigInt) { _ = i.Add(randint()) },
+		func(i *BigInt) { _ = i.Sub(randint()) },
+		func(i *BigInt) { _ = i.Mul(randint()) },
+		func(i *BigInt) { _ = i.Quo(randint()) },
+		func(i *BigInt) { _ = i.AddRaw(rand.Int63()) },
+		func(i *BigInt) { _ = i.SubRaw(rand.Int63()) },
+		func(i *BigInt) { _ = i.MulRaw(rand.Int63()) },
+		func(i *BigInt) { _ = i.QuoRaw(rand.Int63()) },
+		func(i *BigInt) { _ = i.Neg() },
+		func(i *BigInt) { _ = i.Abs() },
+		func(i *BigInt) { _ = i.IsZero() },
+		func(i *BigInt) { _ = i.Sign() },
+		func(i *BigInt) { _ = i.Equal(randint()) },
+		func(i *BigInt) { _ = i.GT(randint()) },
+		func(i *BigInt) { _ = i.LT(randint()) },
+		func(i *BigInt) { _ = i.String() },
+	}
+
+	for i := 0; i < 1000; i++ {
+		n := rand.Int63()
+		ni := NewInt(n)
+
+		for opnum, op := range ops {
+			op(&ni)
+
+			s.Require().Equal(n, ni.Int64(), "Int is modified by operation. tc #%d", opnum)
+			s.Require().Equal(NewInt(n), ni, "Int is modified by operation. tc #%d", opnum)
+		}
+	}
+}
+
+func (s *intTestSuite) TestEncodingTableInt() {
+	var i BigInt
+
+	cases := []struct {
+		i      BigInt
+		jsonBz []byte
+		rawBz  []byte
+	}{
+		{
+			NewInt(0),
+			[]byte("\"0\""),
+			[]byte{0x30},
+		},
+		{
+			NewInt(100),
+			[]byte("\"100\""),
+			[]byte{0x31, 0x30, 0x30},
+		},
+		{
+			NewInt(-100),
+			[]byte("\"-100\""),
+			[]byte{0x2d, 0x31, 0x30, 0x30},
+		},
+		{
+			NewInt(51842),
+			[]byte("\"51842\""),
+			[]byte{0x35, 0x31, 0x38, 0x34, 0x32},
+		},
+		{
+			NewInt(-51842),
+			[]byte("\"-51842\""),
+			[]byte{0x2d, 0x35, 0x31, 0x38, 0x34, 0x32},
+		},
+		{
+			NewInt(19513368),
+			[]byte("\"19513368\""),
+			[]byte{0x31, 0x39, 0x35, 0x31, 0x33, 0x33, 0x36, 0x38},
+		},
+		{
+			NewInt(-19513368),
+			[]byte("\"-19513368\""),
+			[]byte{0x2d, 0x31, 0x39, 0x35, 0x31, 0x33, 0x33, 0x36, 0x38},
+		},
+		{
+			NewInt(999999999999),
+			[]byte("\"999999999999\""),
+			[]byte{0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39},
+		},
+		{
+			NewInt(-999999999999),
+			[]byte("\"-999999999999\""),
+			[]byte{0x2d, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39},
+		},
+	}
+
+	for tcnum, tc := range cases {
+		bz, err := tc.i.MarshalJSON()
+		s.Require().Nil(err, "Error marshaling Int. tc #%d, err %s", tcnum, err)
+		s.Require().Equal(tc.jsonBz, bz, "Marshaled value is different from exported. tc #%d", tcnum)
+
+		err = (&i).UnmarshalJSON(bz)
+		s.Require().Nil(err, "Error unmarshaling Int. tc #%d, err %s", tcnum, err)
+		s.Require().Equal(tc.i, i, "Unmarshaled value is different from exported. tc #%d", tcnum)
+
+		bz, err = tc.i.Marshal()
+		s.Require().Nil(err, "Error marshaling Int. tc #%d, err %s", tcnum, err)
+		s.Require().Equal(tc.rawBz, bz, "Marshaled value is different from exported. tc #%d", tcnum)
+
+		err = (&i).Unmarshal(bz)
+		s.Require().Nil(err, "Error unmarshaling Int. tc #%d, err %s", tcnum, err)
+		s.Require().Equal(tc.i, i, "Unmarshaled value is different from exported. tc #%d", tcnum)
+	}
+}
+
+func (s *intTestSuite) TestEncodingTableUint() {
+	var i sdk.Uint
+
+	cases := []struct {
+		i      sdk.Uint
+		jsonBz []byte
+		rawBz  []byte
+	}{
+		{
+			sdk.NewUint(0),
+			[]byte("\"0\""),
+			[]byte{0x30},
+		},
+		{
+			sdk.NewUint(100),
+			[]byte("\"100\""),
+			[]byte{0x31, 0x30, 0x30},
+		},
+		{
+			sdk.NewUint(51842),
+			[]byte("\"51842\""),
+			[]byte{0x35, 0x31, 0x38, 0x34, 0x32},
+		},
+		{
+			sdk.NewUint(19513368),
+			[]byte("\"19513368\""),
+			[]byte{0x31, 0x39, 0x35, 0x31, 0x33, 0x33, 0x36, 0x38},
+		},
+		{
+			sdk.NewUint(999999999999),
+			[]byte("\"999999999999\""),
+			[]byte{0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39},
+		},
+	}
+
+	for tcnum, tc := range cases {
+		bz, err := tc.i.MarshalJSON()
+		s.Require().Nil(err, "Error marshaling Int. tc #%d, err %s", tcnum, err)
+		s.Require().Equal(tc.jsonBz, bz, "Marshaled value is different from exported. tc #%d", tcnum)
+
+		err = (&i).UnmarshalJSON(bz)
+		s.Require().Nil(err, "Error unmarshaling Int. tc #%d, err %s", tcnum, err)
+		s.Require().Equal(tc.i, i, "Unmarshaled value is different from exported. tc #%d", tcnum)
+
+		bz, err = tc.i.Marshal()
+		s.Require().Nil(err, "Error marshaling Int. tc #%d, err %s", tcnum, err)
+		s.Require().Equal(tc.rawBz, bz, "Marshaled value is different from exported. tc #%d", tcnum)
+
+		err = (&i).Unmarshal(bz)
+		s.Require().Nil(err, "Error unmarshaling Int. tc #%d, err %s", tcnum, err)
+		s.Require().Equal(tc.i, i, "Unmarshaled value is different from exported. tc #%d", tcnum)
+	}
+}
+
+func (s *intTestSuite) TestIntMod() {
+	tests := []struct {
+		name      string
+		x         int64
+		y         int64
+		ret       int64
+		wantPanic bool
+	}{
+		{"3 % 10", 3, 10, 3, false},
+		{"10 % 3", 10, 3, 1, false},
+		{"4 % 2", 4, 2, 0, false},
+		{"2 % 0", 2, 0, 0, true},
+	}
+
+	for _, tt := range tests {
+		if tt.wantPanic {
+			s.Require().Panics(func() { NewInt(tt.x).Mod(NewInt(tt.y)) })
+			s.Require().Panics(func() { NewInt(tt.x).ModRaw(tt.y) })
+			return
+		}
+		s.Require().True(NewInt(tt.x).Mod(NewInt(tt.y)).Equal(NewInt(tt.ret)))
+		s.Require().True(NewInt(tt.x).ModRaw(tt.y).Equal(NewInt(tt.ret)))
+	}
+}
+
+func (s *intTestSuite) TestIntEq() {
+	_, resp, _, _, _ := IntEq(s.T(), ZeroInt(), ZeroInt())
+	s.Require().True(resp)
+	_, resp, _, _, _ = IntEq(s.T(), OneInt(), ZeroInt())
+	s.Require().False(resp)
+}
+
+func TestRoundTripMarshalToInt(t *testing.T) {
+	var values = []int64{
+		0,
+		1,
+		1 << 10,
+		1<<10 - 3,
+		1<<63 - 1,
+		1<<32 - 7,
+		1<<22 - 8,
+	}
+
+	for _, value := range values {
+		value := value
+		t.Run(fmt.Sprintf("%d", value), func(t *testing.T) {
+			t.Parallel()
+
+			var scratch [20]byte
+			iv := NewInt(value)
+			n, err := iv.MarshalTo(scratch[:])
+			if err != nil {
+				t.Fatal(err)
+			}
+			rt := new(BigInt)
+			if err := rt.Unmarshal(scratch[:n]); err != nil {
+				t.Fatal(err)
+			}
+			if !rt.Equal(iv) {
+				t.Fatalf("roundtrip=%q != original=%q", rt, iv)
+			}
+		})
+	}
+}
+
+func (s *intTestSuite) TestEncodingRandom() {
+	for i := 0; i < 1000; i++ {
+		n := rand.Int63()
+		ni := NewInt(n)
+		var ri BigInt
+
+		str, err := ni.Marshal()
+		s.Require().Nil(err)
+		err = (&ri).Unmarshal(str)
+		s.Require().Nil(err)
+
+		s.Require().Equal(ni, ri, "binary mismatch; tc #%d, expected %s, actual %s", i, ni.String(), ri.String())
+		s.Require().True(ni.i != ri.i, "pointer addresses are equal; tc #%d", i)
+
+		bz, err := ni.MarshalJSON()
+		s.Require().Nil(err)
+		err = (&ri).UnmarshalJSON(bz)
+		s.Require().Nil(err)
+
+		s.Require().Equal(ni, ri, "json mismatch; tc #%d, expected %s, actual %s", i, ni.String(), ri.String())
+		s.Require().True(ni.i != ri.i, "pointer addresses are equal; tc #%d", i)
+	}
+
+	for i := 0; i < 1000; i++ {
+		n := rand.Uint64()
+		ni := sdk.NewUint(n)
+
+		var ri sdk.Uint
+
+		str, err := ni.Marshal()
+		s.Require().Nil(err)
+		err = (&ri).Unmarshal(str)
+		s.Require().Nil(err)
+
+		s.Require().Equal(ni, ri, "binary mismatch; tc #%d, expected %s, actual %s", i, ni.String(), ri.String())
+
+		bz, err := ni.MarshalJSON()
+		s.Require().Nil(err)
+		err = (&ri).UnmarshalJSON(bz)
+		s.Require().Nil(err)
+
+		s.Require().Equal(ni, ri, "json mismatch; tc #%d, expected %s, actual %s", i, ni.String(), ri.String())
+	}
+}