Merge pull request #1819: rational -> decimal
* changelog * ... * decimal func working * decimal complete, untested * fixing tests * decimal compile errors resolved * test compile errors * precision multiplier test * 1% laptop battery * fixed TestNewDecFromStr * equalities working * fix bankers round chop * ... * working, some decimal issues resolved * fix rounding error * rounding works * decimal works * ... * deleted rational * rational conversion working * revert changelog * code compiles (not tests) * went through all NewDec, made sure they were converted from NewRat properly * test debugging * all testing bugs besides the json marshalling fixed * json unmarshal * lint * document update * fix lcd test * cli test fix * mostly undo Dece -> Rate * val comments * Efficiency improvements This now caches all of the precision multipliers (as they were all used in non-mutative functions), and caches the precisionInt calculation. (Now it just copies the already calculated value) * Cache another precisionInt() call. * Improve banker rounding efficiency * remove defer, make negation in-place. * chris val comments * bez comments * Aditya comments * ... * val comments * rebasing start * ... * compiling * tests pass * cli fix * anton, cwgoes, val comments * val and jae comments * type * undo reuse quo
This commit is contained in:
@@ -0,0 +1,418 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// NOTE: never use new(Dec) or else we will panic unmarshalling into the
|
||||
// nil embedded big.Int
|
||||
type Dec struct {
|
||||
*big.Int `json:"int"`
|
||||
}
|
||||
|
||||
// number of decimal places
|
||||
const (
|
||||
Precision = 10
|
||||
|
||||
// bytes required to represent the above precision
|
||||
// ceil(log2(9999999999))
|
||||
DecimalPrecisionBits = 34
|
||||
)
|
||||
|
||||
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)
|
||||
)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// nolint - common values
|
||||
func ZeroDec() Dec { return Dec{new(big.Int).Set(zeroInt)} }
|
||||
func OneDec() Dec { return Dec{precisionInt()} }
|
||||
|
||||
// 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 Dec from integer assuming whole number
|
||||
func NewDec(i int64) Dec {
|
||||
return NewDecWithPrec(i, 0)
|
||||
}
|
||||
|
||||
// create a new Dec from integer with decimal place at prec
|
||||
// CONTRACT: prec <= Precision
|
||||
func NewDecWithPrec(i, prec int64) Dec {
|
||||
return Dec{
|
||||
new(big.Int).Mul(big.NewInt(i), precisionMultiplier(prec)),
|
||||
}
|
||||
}
|
||||
|
||||
// create a new Dec from big integer assuming whole numbers
|
||||
// CONTRACT: prec <= Precision
|
||||
func NewDecFromBigInt(i *big.Int) Dec {
|
||||
return NewDecFromBigIntWithPrec(i, 0)
|
||||
}
|
||||
|
||||
// create a new Dec from big integer assuming whole numbers
|
||||
// CONTRACT: prec <= Precision
|
||||
func NewDecFromBigIntWithPrec(i *big.Int, prec int64) Dec {
|
||||
return Dec{
|
||||
new(big.Int).Mul(i, precisionMultiplier(prec)),
|
||||
}
|
||||
}
|
||||
|
||||
// create a new Dec from big integer assuming whole numbers
|
||||
// CONTRACT: prec <= Precision
|
||||
func NewDecFromInt(i Int) Dec {
|
||||
return NewDecFromIntWithPrec(i, 0)
|
||||
}
|
||||
|
||||
// create a new Dec from big integer with decimal place at prec
|
||||
// CONTRACT: prec <= Precision
|
||||
func NewDecFromIntWithPrec(i Int, prec int64) Dec {
|
||||
return Dec{
|
||||
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) (d Dec, err Error) {
|
||||
if len(str) == 0 {
|
||||
return d, ErrUnknownRequest("decimal string is empty")
|
||||
}
|
||||
|
||||
// first extract any negative symbol
|
||||
neg := false
|
||||
if str[0] == '-' {
|
||||
neg = true
|
||||
str = str[1:]
|
||||
}
|
||||
|
||||
if len(str) == 0 {
|
||||
return d, ErrUnknownRequest("decimal string is empty")
|
||||
}
|
||||
|
||||
strs := strings.Split(str, ".")
|
||||
lenDecs := 0
|
||||
combinedStr := strs[0]
|
||||
if len(strs) == 2 {
|
||||
lenDecs = len(strs[1])
|
||||
if lenDecs == 0 || len(combinedStr) == 0 {
|
||||
return d, ErrUnknownRequest("bad decimal length")
|
||||
}
|
||||
combinedStr = combinedStr + strs[1]
|
||||
} else if len(strs) > 2 {
|
||||
return d, ErrUnknownRequest("too many periods to be a decimal string")
|
||||
}
|
||||
|
||||
if lenDecs > Precision {
|
||||
return d, ErrUnknownRequest(
|
||||
fmt.Sprintf("too much precision, maximum %v, len decimal %v", 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 = combinedStr + zeros
|
||||
|
||||
combined, ok := new(big.Int).SetString(combinedStr, 10)
|
||||
if !ok {
|
||||
return d, ErrUnknownRequest(fmt.Sprintf("bad string to integer conversion, combinedStr: %v", combinedStr))
|
||||
}
|
||||
if neg {
|
||||
combined = new(big.Int).Neg(combined)
|
||||
}
|
||||
return Dec{combined}, nil
|
||||
}
|
||||
|
||||
//______________________________________________________________________________________________
|
||||
//nolint
|
||||
func (d Dec) IsZero() bool { return (d.Int).Sign() == 0 } // Is equal to zero
|
||||
func (d Dec) Equal(d2 Dec) bool { return (d.Int).Cmp(d2.Int) == 0 }
|
||||
func (d Dec) GT(d2 Dec) bool { return (d.Int).Cmp(d2.Int) > 0 } // greater than
|
||||
func (d Dec) GTE(d2 Dec) bool { return (d.Int).Cmp(d2.Int) >= 0 } // greater than or equal
|
||||
func (d Dec) LT(d2 Dec) bool { return (d.Int).Cmp(d2.Int) < 0 } // less than
|
||||
func (d Dec) LTE(d2 Dec) bool { return (d.Int).Cmp(d2.Int) <= 0 } // less than or equal
|
||||
func (d Dec) Neg() Dec { return Dec{new(big.Int).Neg(d.Int)} } // reverse the decimal sign
|
||||
|
||||
// addition
|
||||
func (d Dec) Add(d2 Dec) Dec {
|
||||
res := new(big.Int).Add(d.Int, d2.Int)
|
||||
|
||||
if res.BitLen() > 255+DecimalPrecisionBits {
|
||||
panic("Int overflow")
|
||||
}
|
||||
return Dec{res}
|
||||
}
|
||||
|
||||
// subtraction
|
||||
func (d Dec) Sub(d2 Dec) Dec {
|
||||
res := new(big.Int).Sub(d.Int, d2.Int)
|
||||
|
||||
if res.BitLen() > 255+DecimalPrecisionBits {
|
||||
panic("Int overflow")
|
||||
}
|
||||
return Dec{res}
|
||||
}
|
||||
|
||||
// multiplication
|
||||
func (d Dec) Mul(d2 Dec) Dec {
|
||||
mul := new(big.Int).Mul(d.Int, d2.Int)
|
||||
chopped := chopPrecisionAndRound(mul)
|
||||
|
||||
if chopped.BitLen() > 255+DecimalPrecisionBits {
|
||||
panic("Int overflow")
|
||||
}
|
||||
return Dec{chopped}
|
||||
}
|
||||
|
||||
// quotient
|
||||
func (d Dec) Quo(d2 Dec) Dec {
|
||||
|
||||
// multiply precision twice
|
||||
mul := new(big.Int).Mul(d.Int, precisionReuse)
|
||||
mul.Mul(mul, precisionReuse)
|
||||
|
||||
quo := new(big.Int).Quo(mul, d2.Int)
|
||||
chopped := chopPrecisionAndRound(quo)
|
||||
|
||||
if chopped.BitLen() > 255+DecimalPrecisionBits {
|
||||
panic("Int overflow")
|
||||
}
|
||||
return Dec{chopped}
|
||||
}
|
||||
|
||||
func (d Dec) String() string {
|
||||
str := d.ToLeftPaddedWithDecimals(Precision)
|
||||
placement := len(str) - Precision
|
||||
if placement < 0 {
|
||||
panic("too few decimal digits")
|
||||
}
|
||||
return str[:placement] + "." + str[placement:]
|
||||
}
|
||||
|
||||
// TODO panic if negative or if totalDigits < len(initStr)???
|
||||
// evaluate as an integer and return left padded string
|
||||
func (d Dec) ToLeftPaddedWithDecimals(totalDigits int8) string {
|
||||
intStr := d.Int.String()
|
||||
fcode := `%0` + strconv.Itoa(int(totalDigits)) + `s`
|
||||
return fmt.Sprintf(fcode, intStr)
|
||||
}
|
||||
|
||||
// TODO panic if negative or if totalDigits < len(initStr)???
|
||||
// evaluate as an integer and return left padded string
|
||||
func (d Dec) ToLeftPadded(totalDigits int8) string {
|
||||
chopped := chopPrecisionAndRound(d.Int)
|
||||
intStr := chopped.String()
|
||||
fcode := `%0` + strconv.Itoa(int(totalDigits)) + `s`
|
||||
return fmt.Sprintf(fcode, intStr)
|
||||
}
|
||||
|
||||
// ____
|
||||
// __| |__ "chop 'em
|
||||
// ` \ round!"
|
||||
// ___|| ~ _ -bankers
|
||||
// | | __
|
||||
// | | | __|__|__
|
||||
// |_____: / | $$$ |
|
||||
// |________|
|
||||
|
||||
// nolint - go-cyclo
|
||||
// Remove a Precision amount of rightmost digits and perform bankers rounding
|
||||
// on the remainder (gaussian rounding) on the digits which have been removed.
|
||||
//
|
||||
// TODO We should make this function mutate the input. The functions here
|
||||
// don't need to allocate different memory for chopped after computing the
|
||||
// result
|
||||
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 trucated quotient and remainder
|
||||
quo, rem := big.NewInt(0), 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)
|
||||
}
|
||||
}
|
||||
|
||||
// RoundInt64 rounds the decimal using bankers rounding
|
||||
func (d Dec) RoundInt64() int64 {
|
||||
chopped := chopPrecisionAndRound(d.Int)
|
||||
if !chopped.IsInt64() {
|
||||
panic("Int64() out of bound")
|
||||
}
|
||||
return chopped.Int64()
|
||||
}
|
||||
|
||||
// RoundInt round the decimal using bankers rounding
|
||||
func (d Dec) RoundInt() Int {
|
||||
return NewIntFromBigInt(chopPrecisionAndRound(d.Int))
|
||||
}
|
||||
|
||||
//___________________________________________________________________________________
|
||||
|
||||
// reuse nil values
|
||||
var (
|
||||
nilAmino string
|
||||
nilJSON []byte
|
||||
)
|
||||
|
||||
func init() {
|
||||
empty := new(big.Int)
|
||||
bz, err := empty.MarshalText()
|
||||
if err != nil {
|
||||
panic("bad nil amino init")
|
||||
}
|
||||
nilAmino = string(bz)
|
||||
|
||||
nilJSON, err = json.Marshal(string(bz))
|
||||
if err != nil {
|
||||
panic("bad nil json init")
|
||||
}
|
||||
}
|
||||
|
||||
// wraps d.MarshalText()
|
||||
func (d Dec) MarshalAmino() (string, error) {
|
||||
if d.Int == nil {
|
||||
return nilAmino, nil
|
||||
}
|
||||
bz, err := d.Int.MarshalText()
|
||||
return string(bz), err
|
||||
}
|
||||
|
||||
// requires a valid JSON string - strings quotes and calls UnmarshalText
|
||||
func (d *Dec) UnmarshalAmino(text string) (err error) {
|
||||
tempInt := new(big.Int)
|
||||
err = tempInt.UnmarshalText([]byte(text))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.Int = tempInt
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON defines custom encoding scheme
|
||||
func (d Dec) MarshalJSON() ([]byte, error) {
|
||||
if d.Int == nil {
|
||||
return nilJSON, nil
|
||||
}
|
||||
|
||||
bz, err := d.Int.MarshalText()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(string(bz))
|
||||
}
|
||||
|
||||
// UnmarshalJSON defines custom decoding scheme
|
||||
func (d *Dec) UnmarshalJSON(bz []byte) error {
|
||||
if d.Int == nil {
|
||||
d.Int = new(big.Int)
|
||||
}
|
||||
|
||||
var text string
|
||||
err := json.Unmarshal(bz, &text)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return d.Int.UnmarshalText([]byte(text))
|
||||
}
|
||||
|
||||
//___________________________________________________________________________________
|
||||
// helpers
|
||||
|
||||
// test if two decimal arrays are equal
|
||||
func DecsEqual(d1s, d2s []Dec) 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 Dec) Dec {
|
||||
if d1.LT(d2) {
|
||||
return d1
|
||||
}
|
||||
return d2
|
||||
}
|
||||
|
||||
// intended to be used with require/assert: require.True(DecEq(...))
|
||||
func DecEq(t *testing.T, exp, got Dec) (*testing.T, bool, string, Dec, Dec) {
|
||||
return t, exp.Equal(got), "expected:\t%v\ngot:\t\t%v", exp, got
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
wire "github.com/cosmos/cosmos-sdk/wire"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// create a decimal from a decimal string (ex. "1234.5678")
|
||||
func mustNewDecFromStr(t *testing.T, str string) (d Dec) {
|
||||
d, err := NewDecFromStr(str)
|
||||
require.NoError(t, err)
|
||||
return d
|
||||
}
|
||||
|
||||
//_______________________________________
|
||||
|
||||
func TestPrecisionMultiplier(t *testing.T) {
|
||||
res := precisionMultiplier(5)
|
||||
exp := big.NewInt(100000)
|
||||
require.Equal(t, 0, res.Cmp(exp), "equality was incorrect, res %v, exp %v", res, exp)
|
||||
}
|
||||
|
||||
func TestNewDecFromStr(t *testing.T) {
|
||||
largeBigInt, success := new(big.Int).SetString("3144605511029693144278234343371835", 10)
|
||||
require.True(t, success)
|
||||
tests := []struct {
|
||||
decimalStr string
|
||||
expErr bool
|
||||
exp Dec
|
||||
}{
|
||||
{"", true, Dec{}},
|
||||
{"0.-75", true, Dec{}},
|
||||
{"0", false, NewDec(0)},
|
||||
{"1", false, NewDec(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, NewDec(3141203149163817869)},
|
||||
{"314460551102969314427823434337.1835718092488231350",
|
||||
true, NewDecFromBigIntWithPrec(largeBigInt, 4)},
|
||||
{"314460551102969314427823434337.1835",
|
||||
false, NewDecFromBigIntWithPrec(largeBigInt, 4)},
|
||||
{".", true, Dec{}},
|
||||
{".0", true, NewDec(0)},
|
||||
{"1.", true, NewDec(1)},
|
||||
{"foobar", true, Dec{}},
|
||||
{"0.foobar", true, Dec{}},
|
||||
{"0.foobar.", true, Dec{}},
|
||||
}
|
||||
|
||||
for tcIndex, tc := range tests {
|
||||
res, err := NewDecFromStr(tc.decimalStr)
|
||||
if tc.expErr {
|
||||
require.NotNil(t, err, "error expected, decimalStr %v, tc %v", tc.decimalStr, tcIndex)
|
||||
} else {
|
||||
require.Nil(t, err, "unexpected error, decimalStr %v, tc %v", tc.decimalStr, tcIndex)
|
||||
require.True(t, 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 {
|
||||
require.NotNil(t, err, "error expected, decimalStr %v, tc %v", tc.decimalStr, tcIndex)
|
||||
} else {
|
||||
require.Nil(t, err, "unexpected error, decimalStr %v, tc %v", tc.decimalStr, tcIndex)
|
||||
exp := tc.exp.Mul(NewDec(-1))
|
||||
require.True(t, res.Equal(exp), "equality was incorrect, res %v, exp %v, tc %v", res, exp, tcIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEqualities(t *testing.T) {
|
||||
tests := []struct {
|
||||
d1, d2 Dec
|
||||
gt, lt, eq bool
|
||||
}{
|
||||
{NewDec(0), NewDec(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 {
|
||||
require.Equal(t, tc.gt, tc.d1.GT(tc.d2), "GT result is incorrect, tc %d", tcIndex)
|
||||
require.Equal(t, tc.lt, tc.d1.LT(tc.d2), "LT result is incorrect, tc %d", tcIndex)
|
||||
require.Equal(t, tc.eq, tc.d1.Equal(tc.d2), "equality result is incorrect, tc %d", tcIndex)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestDecsEqual(t *testing.T) {
|
||||
tests := []struct {
|
||||
d1s, d2s []Dec
|
||||
eq bool
|
||||
}{
|
||||
{[]Dec{NewDec(0)}, []Dec{NewDec(0)}, true},
|
||||
{[]Dec{NewDec(0)}, []Dec{NewDec(1)}, false},
|
||||
{[]Dec{NewDec(0)}, []Dec{}, false},
|
||||
{[]Dec{NewDec(0), NewDec(1)}, []Dec{NewDec(0), NewDec(1)}, true},
|
||||
{[]Dec{NewDec(1), NewDec(0)}, []Dec{NewDec(1), NewDec(0)}, true},
|
||||
{[]Dec{NewDec(1), NewDec(0)}, []Dec{NewDec(0), NewDec(1)}, false},
|
||||
{[]Dec{NewDec(1), NewDec(0)}, []Dec{NewDec(1)}, false},
|
||||
{[]Dec{NewDec(1), NewDec(2)}, []Dec{NewDec(2), NewDec(4)}, false},
|
||||
{[]Dec{NewDec(3), NewDec(18)}, []Dec{NewDec(1), NewDec(6)}, false},
|
||||
}
|
||||
|
||||
for tcIndex, tc := range tests {
|
||||
require.Equal(t, tc.eq, DecsEqual(tc.d1s, tc.d2s), "equality of decional arrays is incorrect, tc %d", tcIndex)
|
||||
require.Equal(t, tc.eq, DecsEqual(tc.d2s, tc.d1s), "equality of decional arrays is incorrect (converse), tc %d", tcIndex)
|
||||
}
|
||||
}
|
||||
|
||||
func TestArithmetic(t *testing.T) {
|
||||
tests := []struct {
|
||||
d1, d2 Dec
|
||||
expMul, expDiv, expAdd, expSub Dec
|
||||
}{
|
||||
// d1 d2 MUL DIV ADD SUB
|
||||
{NewDec(0), NewDec(0), NewDec(0), NewDec(0), NewDec(0), NewDec(0)},
|
||||
{NewDec(1), NewDec(0), NewDec(0), NewDec(0), NewDec(1), NewDec(1)},
|
||||
{NewDec(0), NewDec(1), NewDec(0), NewDec(0), NewDec(1), NewDec(-1)},
|
||||
{NewDec(0), NewDec(-1), NewDec(0), NewDec(0), NewDec(-1), NewDec(1)},
|
||||
{NewDec(-1), NewDec(0), NewDec(0), NewDec(0), NewDec(-1), NewDec(-1)},
|
||||
|
||||
{NewDec(1), NewDec(1), NewDec(1), NewDec(1), NewDec(2), NewDec(0)},
|
||||
{NewDec(-1), NewDec(-1), NewDec(1), NewDec(1), NewDec(-2), NewDec(0)},
|
||||
{NewDec(1), NewDec(-1), NewDec(-1), NewDec(-1), NewDec(0), NewDec(2)},
|
||||
{NewDec(-1), NewDec(1), NewDec(-1), NewDec(-1), NewDec(0), NewDec(-2)},
|
||||
|
||||
{NewDec(3), NewDec(7), NewDec(21), NewDecWithPrec(4285714286, 10), NewDec(10), NewDec(-4)},
|
||||
{NewDec(2), NewDec(4), NewDec(8), NewDecWithPrec(5, 1), NewDec(6), NewDec(-2)},
|
||||
{NewDec(100), NewDec(100), NewDec(10000), NewDec(1), NewDec(200), NewDec(0)},
|
||||
|
||||
{NewDecWithPrec(15, 1), NewDecWithPrec(15, 1), NewDecWithPrec(225, 2),
|
||||
NewDec(1), NewDec(3), NewDec(0)},
|
||||
{NewDecWithPrec(3333, 4), NewDecWithPrec(333, 4), NewDecWithPrec(1109889, 8),
|
||||
NewDecWithPrec(10009009009, 9), NewDecWithPrec(3666, 4), NewDecWithPrec(3, 1)},
|
||||
}
|
||||
|
||||
for tcIndex, tc := range tests {
|
||||
resAdd := tc.d1.Add(tc.d2)
|
||||
resSub := tc.d1.Sub(tc.d2)
|
||||
resMul := tc.d1.Mul(tc.d2)
|
||||
require.True(t, tc.expAdd.Equal(resAdd), "exp %v, res %v, tc %d", tc.expAdd, resAdd, tcIndex)
|
||||
require.True(t, tc.expSub.Equal(resSub), "exp %v, res %v, tc %d", tc.expSub, resSub, tcIndex)
|
||||
require.True(t, tc.expMul.Equal(resMul), "exp %v, res %v, tc %d", tc.expMul, resMul, tcIndex)
|
||||
|
||||
if tc.d2.IsZero() { // panic for divide by zero
|
||||
require.Panics(t, func() { tc.d1.Quo(tc.d2) })
|
||||
} else {
|
||||
resDiv := tc.d1.Quo(tc.d2)
|
||||
require.True(t, tc.expDiv.Equal(resDiv), "exp %v, res %v, tc %d", tc.expDiv.String(), resDiv.String(), tcIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBankerRoundChop(t *testing.T) {
|
||||
tests := []struct {
|
||||
d1 Dec
|
||||
exp int64
|
||||
}{
|
||||
{mustNewDecFromStr(t, "0.25"), 0},
|
||||
{mustNewDecFromStr(t, "0"), 0},
|
||||
{mustNewDecFromStr(t, "1"), 1},
|
||||
{mustNewDecFromStr(t, "0.75"), 1},
|
||||
{mustNewDecFromStr(t, "0.5"), 0},
|
||||
{mustNewDecFromStr(t, "7.5"), 8},
|
||||
{mustNewDecFromStr(t, "1.5"), 2},
|
||||
{mustNewDecFromStr(t, "2.5"), 2},
|
||||
{mustNewDecFromStr(t, "0.545"), 1}, // 0.545-> 1 even though 5 is first decimal and 1 not even
|
||||
{mustNewDecFromStr(t, "1.545"), 2},
|
||||
}
|
||||
|
||||
for tcIndex, tc := range tests {
|
||||
resNeg := tc.d1.Neg().RoundInt64()
|
||||
require.Equal(t, -1*tc.exp, resNeg, "negative tc %d", tcIndex)
|
||||
|
||||
resPos := tc.d1.RoundInt64()
|
||||
require.Equal(t, tc.exp, resPos, "positive tc %d", tcIndex)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToLeftPadded(t *testing.T) {
|
||||
tests := []struct {
|
||||
dec Dec
|
||||
digits int8
|
||||
exp string
|
||||
}{
|
||||
{mustNewDecFromStr(t, "33.3"), 8, "00000033"},
|
||||
{mustNewDecFromStr(t, "50"), 8, "00000050"},
|
||||
{mustNewDecFromStr(t, "333"), 8, "00000333"},
|
||||
{mustNewDecFromStr(t, "333"), 12, "000000000333"},
|
||||
{mustNewDecFromStr(t, "0.3333"), 8, "00000000"},
|
||||
}
|
||||
for tcIndex, tc := range tests {
|
||||
res := tc.dec.ToLeftPadded(tc.digits)
|
||||
require.Equal(t, tc.exp, res, "incorrect left padding, tc %d", tcIndex)
|
||||
}
|
||||
}
|
||||
|
||||
var cdc = wire.NewCodec()
|
||||
|
||||
func TestZeroDeserializationJSON(t *testing.T) {
|
||||
d := Dec{new(big.Int)}
|
||||
err := cdc.UnmarshalJSON([]byte(`"0"`), &d)
|
||||
require.Nil(t, err)
|
||||
err = cdc.UnmarshalJSON([]byte(`"{}"`), &d)
|
||||
require.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestSerializationText(t *testing.T) {
|
||||
d := mustNewDecFromStr(t, "0.333")
|
||||
|
||||
bz, err := d.MarshalText()
|
||||
require.NoError(t, err)
|
||||
|
||||
d2 := Dec{new(big.Int)}
|
||||
err = d2.UnmarshalText(bz)
|
||||
require.NoError(t, err)
|
||||
require.True(t, d.Equal(d2), "original: %v, unmarshalled: %v", d, d2)
|
||||
}
|
||||
|
||||
func TestSerializationGoWireJSON(t *testing.T) {
|
||||
d := mustNewDecFromStr(t, "0.333")
|
||||
|
||||
bz, err := cdc.MarshalJSON(d)
|
||||
require.NoError(t, err)
|
||||
|
||||
d2 := Dec{new(big.Int)}
|
||||
err = cdc.UnmarshalJSON(bz, &d2)
|
||||
require.NoError(t, err)
|
||||
require.True(t, d.Equal(d2), "original: %v, unmarshalled: %v", d, d2)
|
||||
}
|
||||
|
||||
func TestSerializationGoWireBinary(t *testing.T) {
|
||||
d := mustNewDecFromStr(t, "0.333")
|
||||
|
||||
bz, err := cdc.MarshalBinary(d)
|
||||
require.NoError(t, err)
|
||||
|
||||
var d2 Dec
|
||||
err = cdc.UnmarshalBinary(bz, &d2)
|
||||
require.NoError(t, err)
|
||||
require.True(t, d.Equal(d2), "original: %v, unmarshalled: %v", d, d2)
|
||||
}
|
||||
|
||||
type testDEmbedStruct struct {
|
||||
Field1 string `json:"f1"`
|
||||
Field2 int `json:"f2"`
|
||||
Field3 Dec `json:"f3"`
|
||||
}
|
||||
|
||||
// TODO make work for UnmarshalJSON
|
||||
func TestEmbeddedStructSerializationGoWire(t *testing.T) {
|
||||
obj := testDEmbedStruct{"foo", 10, NewDecWithPrec(1, 3)}
|
||||
bz, err := cdc.MarshalBinary(obj)
|
||||
require.Nil(t, err)
|
||||
|
||||
var obj2 testDEmbedStruct
|
||||
err = cdc.UnmarshalBinary(bz, &obj2)
|
||||
require.Nil(t, err)
|
||||
|
||||
require.Equal(t, obj.Field1, obj2.Field1)
|
||||
require.Equal(t, obj.Field2, obj2.Field2)
|
||||
require.True(t, obj.Field3.Equal(obj2.Field3), "original: %v, unmarshalled: %v", obj, obj2)
|
||||
}
|
||||
|
||||
func TestStringOverflow(t *testing.T) {
|
||||
// two random 64 bit primes
|
||||
dec1, err := NewDecFromStr("51643150036226787134389711697696177267")
|
||||
require.NoError(t, err)
|
||||
dec2, err := NewDecFromStr("-31798496660535729618459429845579852627")
|
||||
require.NoError(t, err)
|
||||
dec3 := dec1.Add(dec2)
|
||||
require.Equal(t,
|
||||
"19844653375691057515930281852116324640.0000000000",
|
||||
dec3.String(),
|
||||
)
|
||||
}
|
||||
@@ -1,262 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// "that's one big rat!"
|
||||
// ______
|
||||
// / / /\ \____oo
|
||||
// __ /___...._____ _\o
|
||||
// __| |_ |_
|
||||
|
||||
// NOTE: never use new(Rat) or else
|
||||
// we will panic unmarshalling into the
|
||||
// nil embedded big.Rat
|
||||
type Rat struct {
|
||||
*big.Rat `json:"rat"`
|
||||
}
|
||||
|
||||
// nolint - common values
|
||||
func ZeroRat() Rat { return Rat{big.NewRat(0, 1)} }
|
||||
func OneRat() Rat { return Rat{big.NewRat(1, 1)} }
|
||||
|
||||
// New - create a new Rat from integers
|
||||
func NewRat(Numerator int64, Denominator ...int64) Rat {
|
||||
switch len(Denominator) {
|
||||
case 0:
|
||||
return Rat{big.NewRat(Numerator, 1)}
|
||||
case 1:
|
||||
return Rat{big.NewRat(Numerator, Denominator[0])}
|
||||
default:
|
||||
panic("improper use of New, can only have one denominator")
|
||||
}
|
||||
}
|
||||
|
||||
func getNumeratorDenominator(str []string, prec int) (numerator string, denom int64, err Error) {
|
||||
switch len(str) {
|
||||
case 1:
|
||||
if len(str[0]) == 0 {
|
||||
return "", 0, ErrUnknownRequest("not a decimal string")
|
||||
}
|
||||
numerator = str[0]
|
||||
return numerator, 1, nil
|
||||
case 2:
|
||||
if len(str[0]) == 0 || len(str[1]) == 0 {
|
||||
return "", 0, ErrUnknownRequest("not a decimal string")
|
||||
}
|
||||
if len(str[1]) > prec {
|
||||
return "", 0, ErrUnknownRequest("string has too many decimals")
|
||||
}
|
||||
numerator = str[0] + str[1]
|
||||
len := int64(len(str[1]))
|
||||
denom = new(big.Int).Exp(big.NewInt(10), big.NewInt(len), nil).Int64()
|
||||
return numerator, denom, nil
|
||||
default:
|
||||
return "", 0, ErrUnknownRequest("not a decimal string")
|
||||
}
|
||||
}
|
||||
|
||||
// create a rational from decimal string or integer string
|
||||
// precision is the number of values after the decimal point which should be read
|
||||
func NewRatFromDecimal(decimalStr string, prec int) (f Rat, err Error) {
|
||||
// first extract any negative symbol
|
||||
if len(decimalStr) == 0 {
|
||||
return f, ErrUnknownRequest("decimal string is empty")
|
||||
}
|
||||
|
||||
neg := false
|
||||
if string(decimalStr[0]) == "-" {
|
||||
neg = true
|
||||
decimalStr = decimalStr[1:]
|
||||
}
|
||||
|
||||
str := strings.Split(decimalStr, ".")
|
||||
|
||||
numStr, denom, err := getNumeratorDenominator(str, prec)
|
||||
if err != nil {
|
||||
return f, err
|
||||
}
|
||||
|
||||
num, errConv := strconv.Atoi(numStr)
|
||||
if errConv != nil && strings.HasSuffix(errConv.Error(), "value out of range") {
|
||||
// resort to big int, don't make this default option for efficiency
|
||||
numBig, success := new(big.Int).SetString(numStr, 10)
|
||||
if success != true {
|
||||
return f, ErrUnknownRequest("not a decimal string")
|
||||
}
|
||||
|
||||
if neg {
|
||||
numBig.Neg(numBig)
|
||||
}
|
||||
|
||||
return NewRatFromBigInt(numBig, big.NewInt(denom)), nil
|
||||
} else if errConv != nil {
|
||||
return f, ErrUnknownRequest("not a decimal string")
|
||||
}
|
||||
|
||||
if neg {
|
||||
num *= -1
|
||||
}
|
||||
|
||||
return NewRat(int64(num), denom), nil
|
||||
}
|
||||
|
||||
// NewRatFromBigInt constructs Rat from big.Int
|
||||
func NewRatFromBigInt(num *big.Int, denom ...*big.Int) Rat {
|
||||
switch len(denom) {
|
||||
case 0:
|
||||
return Rat{new(big.Rat).SetInt(num)}
|
||||
case 1:
|
||||
return Rat{new(big.Rat).SetFrac(num, denom[0])}
|
||||
default:
|
||||
panic("improper use of NewRatFromBigInt, can only have one denominator")
|
||||
}
|
||||
}
|
||||
|
||||
// NewRatFromInt constructs Rat from Int
|
||||
func NewRatFromInt(num Int, denom ...Int) Rat {
|
||||
switch len(denom) {
|
||||
case 0:
|
||||
return Rat{new(big.Rat).SetInt(num.BigInt())}
|
||||
case 1:
|
||||
return Rat{new(big.Rat).SetFrac(num.BigInt(), denom[0].BigInt())}
|
||||
default:
|
||||
panic("improper use of NewRatFromBigInt, can only have one denominator")
|
||||
}
|
||||
}
|
||||
|
||||
//nolint
|
||||
func (r Rat) Num() Int { return Int{r.Rat.Num()} } // Num - return the numerator
|
||||
func (r Rat) Denom() Int { return Int{r.Rat.Denom()} } // Denom - return the denominator
|
||||
func (r Rat) IsZero() bool { return r.Num().IsZero() } // IsZero - Is the Rat equal to zero
|
||||
func (r Rat) Equal(r2 Rat) bool { return (r.Rat).Cmp(r2.Rat) == 0 }
|
||||
func (r Rat) GT(r2 Rat) bool { return (r.Rat).Cmp(r2.Rat) == 1 } // greater than
|
||||
func (r Rat) GTE(r2 Rat) bool { return !r.LT(r2) } // greater than or equal
|
||||
func (r Rat) LT(r2 Rat) bool { return (r.Rat).Cmp(r2.Rat) == -1 } // less than
|
||||
func (r Rat) LTE(r2 Rat) bool { return !r.GT(r2) } // less than or equal
|
||||
func (r Rat) Mul(r2 Rat) Rat { return Rat{new(big.Rat).Mul(r.Rat, r2.Rat)} } // Mul - multiplication
|
||||
func (r Rat) Quo(r2 Rat) Rat { return Rat{new(big.Rat).Quo(r.Rat, r2.Rat)} } // Quo - quotient
|
||||
func (r Rat) Add(r2 Rat) Rat { return Rat{new(big.Rat).Add(r.Rat, r2.Rat)} } // Add - addition
|
||||
func (r Rat) Sub(r2 Rat) Rat { return Rat{new(big.Rat).Sub(r.Rat, r2.Rat)} } // Sub - subtraction
|
||||
func (r Rat) String() string { return r.Rat.String() }
|
||||
func (r Rat) FloatString() string { return r.Rat.FloatString(10) } // a human-friendly string format. The last digit is rounded to nearest, with halves rounded away from zero.
|
||||
|
||||
var (
|
||||
zero = big.NewInt(0)
|
||||
one = big.NewInt(1)
|
||||
two = big.NewInt(2)
|
||||
five = big.NewInt(5)
|
||||
nFive = big.NewInt(-5)
|
||||
ten = big.NewInt(10)
|
||||
)
|
||||
|
||||
// evaluate the rational using bankers rounding
|
||||
func (r Rat) EvaluateBig() *big.Int {
|
||||
|
||||
num := r.Rat.Num()
|
||||
denom := r.Rat.Denom()
|
||||
|
||||
d, rem := new(big.Int), new(big.Int)
|
||||
d.QuoRem(num, denom, rem)
|
||||
if rem.Cmp(zero) == 0 { // is the remainder zero
|
||||
return d
|
||||
}
|
||||
|
||||
// evaluate the remainder using bankers rounding
|
||||
tenNum := new(big.Int).Mul(num, ten)
|
||||
tenD := new(big.Int).Mul(d, ten)
|
||||
remainderDigit := new(big.Int).Sub(new(big.Int).Quo(tenNum, denom), tenD) // get the first remainder digit
|
||||
isFinalDigit := (new(big.Int).Rem(tenNum, denom).Cmp(zero) == 0) // is this the final digit in the remainder?
|
||||
|
||||
switch {
|
||||
case isFinalDigit && (remainderDigit.Cmp(five) == 0 || remainderDigit.Cmp(nFive) == 0):
|
||||
dRem2 := new(big.Int).Rem(d, two)
|
||||
return new(big.Int).Add(d, dRem2) // always rounds to the even number
|
||||
case remainderDigit.Cmp(five) != -1: //remainderDigit >= 5:
|
||||
d.Add(d, one)
|
||||
case remainderDigit.Cmp(nFive) != 1: //remainderDigit <= -5:
|
||||
d.Sub(d, one)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// RoundInt64 rounds the rational using bankers rounding
|
||||
func (r Rat) RoundInt64() int64 {
|
||||
return r.EvaluateBig().Int64()
|
||||
}
|
||||
|
||||
// RoundInt round the rational using bankers rounding
|
||||
func (r Rat) RoundInt() Int {
|
||||
return NewIntFromBigInt(r.EvaluateBig())
|
||||
}
|
||||
|
||||
// round Rat with the provided precisionFactor
|
||||
func (r Rat) Round(precisionFactor int64) Rat {
|
||||
rTen := Rat{new(big.Rat).Mul(r.Rat, big.NewRat(precisionFactor, 1))}
|
||||
return Rat{big.NewRat(rTen.RoundInt64(), precisionFactor)}
|
||||
}
|
||||
|
||||
// TODO panic if negative or if totalDigits < len(initStr)???
|
||||
// evaluate as an integer and return left padded string
|
||||
func (r Rat) ToLeftPadded(totalDigits int8) string {
|
||||
intStr := r.EvaluateBig().String()
|
||||
fcode := `%0` + strconv.Itoa(int(totalDigits)) + `s`
|
||||
return fmt.Sprintf(fcode, intStr)
|
||||
}
|
||||
|
||||
//___________________________________________________________________________________
|
||||
|
||||
//Wraps r.MarshalText().
|
||||
func (r Rat) MarshalAmino() (string, error) {
|
||||
if r.Rat == nil {
|
||||
r.Rat = new(big.Rat)
|
||||
}
|
||||
bz, err := r.Rat.MarshalText()
|
||||
return string(bz), err
|
||||
}
|
||||
|
||||
// Requires a valid JSON string - strings quotes and calls UnmarshalText
|
||||
func (r *Rat) UnmarshalAmino(text string) (err error) {
|
||||
tempRat := big.NewRat(0, 1)
|
||||
err = tempRat.UnmarshalText([]byte(text))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.Rat = tempRat
|
||||
return nil
|
||||
}
|
||||
|
||||
//___________________________________________________________________________________
|
||||
// helpers
|
||||
|
||||
// test if two rat arrays are equal
|
||||
func RatsEqual(r1s, r2s []Rat) bool {
|
||||
if len(r1s) != len(r2s) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i, r1 := range r1s {
|
||||
if !r1.Equal(r2s[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// intended to be used with require/assert: require.True(RatEq(...))
|
||||
func RatEq(t *testing.T, exp, got Rat) (*testing.T, bool, string, Rat, Rat) {
|
||||
return t, exp.Equal(got), "expected:\t%v\ngot:\t\t%v", exp, got
|
||||
}
|
||||
|
||||
// minimum rational between two
|
||||
func MinRat(r1, r2 Rat) Rat {
|
||||
if r1.LT(r2) {
|
||||
return r1
|
||||
}
|
||||
return r2
|
||||
}
|
||||
@@ -1,402 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
wire "github.com/cosmos/cosmos-sdk/wire"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
require.Equal(t, NewRat(1), NewRat(1, 1))
|
||||
require.Equal(t, NewRat(100), NewRat(100, 1))
|
||||
require.Equal(t, NewRat(-1), NewRat(-1, 1))
|
||||
require.Equal(t, NewRat(-100), NewRat(-100, 1))
|
||||
require.Equal(t, NewRat(0), NewRat(0, 1))
|
||||
|
||||
// do not allow for more than 2 variables
|
||||
require.Panics(t, func() { NewRat(1, 1, 1) })
|
||||
}
|
||||
|
||||
func TestNewFromDecimal(t *testing.T) {
|
||||
largeBigInt, success := new(big.Int).SetString("3109736052979742687701388262607869", 10)
|
||||
require.True(t, success)
|
||||
tests := []struct {
|
||||
decimalStr string
|
||||
expErr bool
|
||||
exp Rat
|
||||
}{
|
||||
{"", true, Rat{}},
|
||||
{"0", false, NewRat(0)},
|
||||
{"1", false, NewRat(1)},
|
||||
{"1.1", false, NewRat(11, 10)},
|
||||
{"0.75", false, NewRat(3, 4)},
|
||||
{"0.8", false, NewRat(4, 5)},
|
||||
{"0.11111", true, NewRat(1111, 10000)},
|
||||
{"628240629832763.5738930323617075341", true, NewRat(3141203149163817869, 5000)},
|
||||
{"621947210595948537540277652521.5738930323617075341",
|
||||
true, NewRatFromBigInt(largeBigInt, big.NewInt(5000))},
|
||||
{"628240629832763.5738", false, NewRat(3141203149163817869, 5000)},
|
||||
{"621947210595948537540277652521.5738",
|
||||
false, NewRatFromBigInt(largeBigInt, big.NewInt(5000))},
|
||||
{".", true, Rat{}},
|
||||
{".0", true, Rat{}},
|
||||
{"1.", true, Rat{}},
|
||||
{"foobar", true, Rat{}},
|
||||
{"0.foobar", true, Rat{}},
|
||||
{"0.foobar.", true, Rat{}},
|
||||
}
|
||||
|
||||
for tcIndex, tc := range tests {
|
||||
res, err := NewRatFromDecimal(tc.decimalStr, 4)
|
||||
if tc.expErr {
|
||||
require.NotNil(t, err, tc.decimalStr, "error expected, tc #%d", tcIndex)
|
||||
} else {
|
||||
require.Nil(t, err, tc.decimalStr, "unexpected error, tc #%d", tcIndex)
|
||||
require.True(t, res.Equal(tc.exp), tc.decimalStr, "equality was incorrect, tc #%d", tcIndex)
|
||||
}
|
||||
|
||||
// negative tc
|
||||
res, err = NewRatFromDecimal("-"+tc.decimalStr, 4)
|
||||
if tc.expErr {
|
||||
require.NotNil(t, err, tc.decimalStr, "error expected (negative case), tc #%d", tcIndex)
|
||||
} else {
|
||||
require.Nil(t, err, tc.decimalStr, "unexpected error (negative case), tc #%d", tcIndex)
|
||||
require.True(t, res.Equal(tc.exp.Mul(NewRat(-1))), tc.decimalStr, "equality was incorrect (negative case), tc #%d", tcIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEqualities(t *testing.T) {
|
||||
tests := []struct {
|
||||
r1, r2 Rat
|
||||
gt, lt, eq bool
|
||||
}{
|
||||
{NewRat(0), NewRat(0), false, false, true},
|
||||
{NewRat(0, 100), NewRat(0, 10000), false, false, true},
|
||||
{NewRat(100), NewRat(100), false, false, true},
|
||||
{NewRat(-100), NewRat(-100), false, false, true},
|
||||
{NewRat(-100, -1), NewRat(100), false, false, true},
|
||||
{NewRat(-1, 1), NewRat(1, -1), false, false, true},
|
||||
{NewRat(1, -1), NewRat(-1, 1), false, false, true},
|
||||
{NewRat(3, 7), NewRat(3, 7), false, false, true},
|
||||
|
||||
{NewRat(0), NewRat(3, 7), false, true, false},
|
||||
{NewRat(0), NewRat(100), false, true, false},
|
||||
{NewRat(-1), NewRat(3, 7), false, true, false},
|
||||
{NewRat(-1), NewRat(100), false, true, false},
|
||||
{NewRat(1, 7), NewRat(100), false, true, false},
|
||||
{NewRat(1, 7), NewRat(3, 7), false, true, false},
|
||||
{NewRat(-3, 7), NewRat(-1, 7), false, true, false},
|
||||
|
||||
{NewRat(3, 7), NewRat(0), true, false, false},
|
||||
{NewRat(100), NewRat(0), true, false, false},
|
||||
{NewRat(3, 7), NewRat(-1), true, false, false},
|
||||
{NewRat(100), NewRat(-1), true, false, false},
|
||||
{NewRat(100), NewRat(1, 7), true, false, false},
|
||||
{NewRat(3, 7), NewRat(1, 7), true, false, false},
|
||||
{NewRat(-1, 7), NewRat(-3, 7), true, false, false},
|
||||
}
|
||||
|
||||
for tcIndex, tc := range tests {
|
||||
require.Equal(t, tc.gt, tc.r1.GT(tc.r2), "GT result is incorrect, tc #%d", tcIndex)
|
||||
require.Equal(t, tc.lt, tc.r1.LT(tc.r2), "LT result is incorrect, tc #%d", tcIndex)
|
||||
require.Equal(t, tc.eq, tc.r1.Equal(tc.r2), "equality result is incorrect, tc #%d", tcIndex)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestArithmetic(t *testing.T) {
|
||||
tests := []struct {
|
||||
r1, r2 Rat
|
||||
resMul, resDiv, resAdd, resSub Rat
|
||||
}{
|
||||
// r1 r2 MUL DIV ADD SUB
|
||||
{NewRat(0), NewRat(0), NewRat(0), NewRat(0), NewRat(0), NewRat(0)},
|
||||
{NewRat(1), NewRat(0), NewRat(0), NewRat(0), NewRat(1), NewRat(1)},
|
||||
{NewRat(0), NewRat(1), NewRat(0), NewRat(0), NewRat(1), NewRat(-1)},
|
||||
{NewRat(0), NewRat(-1), NewRat(0), NewRat(0), NewRat(-1), NewRat(1)},
|
||||
{NewRat(-1), NewRat(0), NewRat(0), NewRat(0), NewRat(-1), NewRat(-1)},
|
||||
|
||||
{NewRat(1), NewRat(1), NewRat(1), NewRat(1), NewRat(2), NewRat(0)},
|
||||
{NewRat(-1), NewRat(-1), NewRat(1), NewRat(1), NewRat(-2), NewRat(0)},
|
||||
{NewRat(1), NewRat(-1), NewRat(-1), NewRat(-1), NewRat(0), NewRat(2)},
|
||||
{NewRat(-1), NewRat(1), NewRat(-1), NewRat(-1), NewRat(0), NewRat(-2)},
|
||||
|
||||
{NewRat(3), NewRat(7), NewRat(21), NewRat(3, 7), NewRat(10), NewRat(-4)},
|
||||
{NewRat(2), NewRat(4), NewRat(8), NewRat(1, 2), NewRat(6), NewRat(-2)},
|
||||
{NewRat(100), NewRat(100), NewRat(10000), NewRat(1), NewRat(200), NewRat(0)},
|
||||
|
||||
{NewRat(3, 2), NewRat(3, 2), NewRat(9, 4), NewRat(1), NewRat(3), NewRat(0)},
|
||||
{NewRat(3, 7), NewRat(7, 3), NewRat(1), NewRat(9, 49), NewRat(58, 21), NewRat(-40, 21)},
|
||||
{NewRat(1, 21), NewRat(11, 5), NewRat(11, 105), NewRat(5, 231), NewRat(236, 105), NewRat(-226, 105)},
|
||||
{NewRat(-21), NewRat(3, 7), NewRat(-9), NewRat(-49), NewRat(-144, 7), NewRat(-150, 7)},
|
||||
{NewRat(100), NewRat(1, 7), NewRat(100, 7), NewRat(700), NewRat(701, 7), NewRat(699, 7)},
|
||||
}
|
||||
|
||||
for tcIndex, tc := range tests {
|
||||
require.True(t, tc.resMul.Equal(tc.r1.Mul(tc.r2)), "r1 %v, r2 %v. tc #%d", tc.r1.Rat, tc.r2.Rat, tcIndex)
|
||||
require.True(t, tc.resAdd.Equal(tc.r1.Add(tc.r2)), "r1 %v, r2 %v. tc #%d", tc.r1.Rat, tc.r2.Rat, tcIndex)
|
||||
require.True(t, tc.resSub.Equal(tc.r1.Sub(tc.r2)), "r1 %v, r2 %v. tc #%d", tc.r1.Rat, tc.r2.Rat, tcIndex)
|
||||
|
||||
if tc.r2.Num().IsZero() { // panic for divide by zero
|
||||
require.Panics(t, func() { tc.r1.Quo(tc.r2) })
|
||||
} else {
|
||||
require.True(t, tc.resDiv.Equal(tc.r1.Quo(tc.r2)), "r1 %v, r2 %v. tc #%d", tc.r1.Rat, tc.r2.Rat, tcIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluate(t *testing.T) {
|
||||
tests := []struct {
|
||||
r1 Rat
|
||||
res int64
|
||||
}{
|
||||
{NewRat(0), 0},
|
||||
{NewRat(1), 1},
|
||||
{NewRat(1, 4), 0},
|
||||
{NewRat(1, 2), 0},
|
||||
{NewRat(3, 4), 1},
|
||||
{NewRat(5, 6), 1},
|
||||
{NewRat(3, 2), 2},
|
||||
{NewRat(5, 2), 2},
|
||||
{NewRat(6, 11), 1}, // 0.545-> 1 even though 5 is first decimal and 1 not even
|
||||
{NewRat(17, 11), 2}, // 1.545
|
||||
{NewRat(5, 11), 0},
|
||||
{NewRat(16, 11), 1},
|
||||
{NewRat(113, 12), 9},
|
||||
}
|
||||
|
||||
for tcIndex, tc := range tests {
|
||||
require.Equal(t, tc.res, tc.r1.RoundInt64(), "%v. tc #%d", tc.r1, tcIndex)
|
||||
require.Equal(t, tc.res*-1, tc.r1.Mul(NewRat(-1)).RoundInt64(), "%v. tc #%d", tc.r1.Mul(NewRat(-1)), tcIndex)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRound(t *testing.T) {
|
||||
many3 := "333333333333333333333333333333333333333333333"
|
||||
many7 := "777777777777777777777777777777777777777777777"
|
||||
big3, worked := new(big.Int).SetString(many3, 10)
|
||||
require.True(t, worked)
|
||||
big7, worked := new(big.Int).SetString(many7, 10)
|
||||
require.True(t, worked)
|
||||
|
||||
tests := []struct {
|
||||
r, res Rat
|
||||
precFactor int64
|
||||
}{
|
||||
{NewRat(333, 777), NewRat(429, 1000), 1000},
|
||||
{Rat{new(big.Rat).SetFrac(big3, big7)}, NewRat(429, 1000), 1000},
|
||||
{Rat{new(big.Rat).SetFrac(big3, big7)}, Rat{big.NewRat(4285714286, 10000000000)}, 10000000000},
|
||||
{NewRat(1, 2), NewRat(1, 2), 1000},
|
||||
}
|
||||
|
||||
for tcIndex, tc := range tests {
|
||||
require.Equal(t, tc.res, tc.r.Round(tc.precFactor), "%v", tc.r, "incorrect rounding, tc #%d", tcIndex)
|
||||
negR1, negRes := tc.r.Mul(NewRat(-1)), tc.res.Mul(NewRat(-1))
|
||||
require.Equal(t, negRes, negR1.Round(tc.precFactor), "%v", negR1, "incorrect rounding (negative case), tc #%d", tcIndex)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToLeftPadded(t *testing.T) {
|
||||
tests := []struct {
|
||||
rat Rat
|
||||
digits int8
|
||||
res string
|
||||
}{
|
||||
{NewRat(100, 3), 8, "00000033"},
|
||||
{NewRat(1, 3), 8, "00000000"},
|
||||
{NewRat(100, 2), 8, "00000050"},
|
||||
{NewRat(1000, 3), 8, "00000333"},
|
||||
{NewRat(1000, 3), 12, "000000000333"},
|
||||
}
|
||||
for tcIndex, tc := range tests {
|
||||
require.Equal(t, tc.res, tc.rat.ToLeftPadded(tc.digits), "incorrect left padding, tc #%d", tcIndex)
|
||||
}
|
||||
}
|
||||
|
||||
var cdc = wire.NewCodec() //var jsonCdc JSONCodec // TODO wire.Codec
|
||||
|
||||
func TestZeroSerializationJSON(t *testing.T) {
|
||||
r := NewRat(0, 1)
|
||||
err := cdc.UnmarshalJSON([]byte(`"0/1"`), &r)
|
||||
require.Nil(t, err)
|
||||
err = cdc.UnmarshalJSON([]byte(`"0/0"`), &r)
|
||||
require.NotNil(t, err)
|
||||
err = cdc.UnmarshalJSON([]byte(`"1/0"`), &r)
|
||||
require.NotNil(t, err)
|
||||
err = cdc.UnmarshalJSON([]byte(`"{}"`), &r)
|
||||
require.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestSerializationText(t *testing.T) {
|
||||
r := NewRat(1, 3)
|
||||
|
||||
bz, err := r.MarshalText()
|
||||
require.NoError(t, err)
|
||||
|
||||
var r2 = Rat{new(big.Rat)}
|
||||
err = r2.UnmarshalText(bz)
|
||||
require.NoError(t, err)
|
||||
require.True(t, r.Equal(r2), "original: %v, unmarshalled: %v", r, r2)
|
||||
}
|
||||
|
||||
func TestSerializationGoWireJSON(t *testing.T) {
|
||||
r := NewRat(1, 3)
|
||||
bz, err := cdc.MarshalJSON(r)
|
||||
require.NoError(t, err)
|
||||
|
||||
var r2 Rat
|
||||
err = cdc.UnmarshalJSON(bz, &r2)
|
||||
require.NoError(t, err)
|
||||
require.True(t, r.Equal(r2), "original: %v, unmarshalled: %v", r, r2)
|
||||
}
|
||||
|
||||
func TestSerializationGoWireBinary(t *testing.T) {
|
||||
r := NewRat(1, 3)
|
||||
bz, err := cdc.MarshalBinary(r)
|
||||
require.NoError(t, err)
|
||||
|
||||
var r2 Rat
|
||||
err = cdc.UnmarshalBinary(bz, &r2)
|
||||
require.NoError(t, err)
|
||||
require.True(t, r.Equal(r2), "original: %v, unmarshalled: %v", r, r2)
|
||||
}
|
||||
|
||||
type testEmbedStruct struct {
|
||||
Field1 string `json:"f1"`
|
||||
Field2 int `json:"f2"`
|
||||
Field3 Rat `json:"f3"`
|
||||
}
|
||||
|
||||
func TestEmbeddedStructSerializationGoWire(t *testing.T) {
|
||||
obj := testEmbedStruct{"foo", 10, NewRat(1, 3)}
|
||||
bz, err := cdc.MarshalJSON(obj)
|
||||
require.Nil(t, err)
|
||||
|
||||
var obj2 testEmbedStruct
|
||||
err = cdc.UnmarshalJSON(bz, &obj2)
|
||||
require.Nil(t, err)
|
||||
|
||||
require.Equal(t, obj.Field1, obj2.Field1)
|
||||
require.Equal(t, obj.Field2, obj2.Field2)
|
||||
require.True(t, obj.Field3.Equal(obj2.Field3), "original: %v, unmarshalled: %v", obj, obj2)
|
||||
}
|
||||
|
||||
func TestRatsEqual(t *testing.T) {
|
||||
tests := []struct {
|
||||
r1s, r2s []Rat
|
||||
eq bool
|
||||
}{
|
||||
{[]Rat{NewRat(0)}, []Rat{NewRat(0)}, true},
|
||||
{[]Rat{NewRat(0)}, []Rat{NewRat(1)}, false},
|
||||
{[]Rat{NewRat(0)}, []Rat{}, false},
|
||||
{[]Rat{NewRat(0), NewRat(1)}, []Rat{NewRat(0), NewRat(1)}, true},
|
||||
{[]Rat{NewRat(1), NewRat(0)}, []Rat{NewRat(1), NewRat(0)}, true},
|
||||
{[]Rat{NewRat(1), NewRat(0)}, []Rat{NewRat(0), NewRat(1)}, false},
|
||||
{[]Rat{NewRat(1), NewRat(0)}, []Rat{NewRat(1)}, false},
|
||||
{[]Rat{NewRat(1), NewRat(2)}, []Rat{NewRat(2), NewRat(4)}, false},
|
||||
{[]Rat{NewRat(3), NewRat(18)}, []Rat{NewRat(1), NewRat(6)}, false},
|
||||
}
|
||||
|
||||
for tcIndex, tc := range tests {
|
||||
require.Equal(t, tc.eq, RatsEqual(tc.r1s, tc.r2s), "equality of rational arrays is incorrect, tc #%d", tcIndex)
|
||||
require.Equal(t, tc.eq, RatsEqual(tc.r2s, tc.r1s), "equality of rational arrays is incorrect (converse), tc #%d", tcIndex)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestStringOverflow(t *testing.T) {
|
||||
// two random 64 bit primes
|
||||
rat1 := NewRat(5164315003622678713, 4389711697696177267)
|
||||
rat2 := NewRat(-3179849666053572961, 8459429845579852627)
|
||||
rat3 := rat1.Add(rat2)
|
||||
require.Equal(t,
|
||||
"29728537197630860939575850336935951464/37134458148982045574552091851127630409",
|
||||
rat3.String(),
|
||||
)
|
||||
}
|
||||
|
||||
// Tests below uses randomness
|
||||
// Since we are using *big.Rat as underlying value
|
||||
// and (U/)Int is immutable value(see TestImmutability(U/)Int)
|
||||
// it is safe to use randomness in the tests
|
||||
func TestArithRat(t *testing.T) {
|
||||
for i := 0; i < 20; i++ {
|
||||
n1 := NewInt(int64(rand.Int31()))
|
||||
d1 := NewInt(int64(rand.Int31()))
|
||||
rat1 := NewRatFromInt(n1, d1)
|
||||
|
||||
n2 := NewInt(int64(rand.Int31()))
|
||||
d2 := NewInt(int64(rand.Int31()))
|
||||
rat2 := NewRatFromInt(n2, d2)
|
||||
|
||||
n1d2 := n1.Mul(d2)
|
||||
n2d1 := n2.Mul(d1)
|
||||
|
||||
cases := []struct {
|
||||
nres Int
|
||||
dres Int
|
||||
rres Rat
|
||||
}{
|
||||
{n1d2.Add(n2d1), d1.Mul(d2), rat1.Add(rat2)},
|
||||
{n1d2.Sub(n2d1), d1.Mul(d2), rat1.Sub(rat2)},
|
||||
{n1.Mul(n2), d1.Mul(d2), rat1.Mul(rat2)},
|
||||
{n1d2, n2d1, rat1.Quo(rat2)},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
require.Equal(t, NewRatFromInt(tc.nres, tc.dres), tc.rres)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompRat(t *testing.T) {
|
||||
for i := 0; i < 20; i++ {
|
||||
n1 := NewInt(int64(rand.Int31()))
|
||||
d1 := NewInt(int64(rand.Int31()))
|
||||
rat1 := NewRatFromInt(n1, d1)
|
||||
|
||||
n2 := NewInt(int64(rand.Int31()))
|
||||
d2 := NewInt(int64(rand.Int31()))
|
||||
rat2 := NewRatFromInt(n2, d2)
|
||||
|
||||
n1d2 := n1.Mul(d2)
|
||||
n2d1 := n2.Mul(d1)
|
||||
|
||||
cases := []struct {
|
||||
ires bool
|
||||
rres bool
|
||||
}{
|
||||
{n1d2.Equal(n2d1), rat1.Equal(rat2)},
|
||||
{n1d2.GT(n2d1), rat1.GT(rat2)},
|
||||
{n1d2.LT(n2d1), rat1.LT(rat2)},
|
||||
{n1d2.GT(n2d1) || n1d2.Equal(n2d1), rat1.GTE(rat2)},
|
||||
{n1d2.LT(n2d1) || n1d2.Equal(n2d1), rat1.LTE(rat2)},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
require.Equal(t, tc.ires, tc.rres)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestImmutabilityRat(t *testing.T) {
|
||||
for i := 0; i < 20; i++ {
|
||||
n := int64(rand.Int31())
|
||||
r := NewRat(n)
|
||||
z := ZeroRat()
|
||||
o := OneRat()
|
||||
|
||||
r.Add(z)
|
||||
r.Sub(z)
|
||||
r.Mul(o)
|
||||
r.Quo(o)
|
||||
|
||||
require.Equal(t, n, r.RoundInt64())
|
||||
require.True(t, NewRat(n).Equal(r))
|
||||
}
|
||||
|
||||
}
|
||||
+6
-6
@@ -42,9 +42,9 @@ type Validator interface {
|
||||
GetStatus() BondStatus // status of the validator
|
||||
GetOwner() AccAddress // owner AccAddress to receive/return validators coins
|
||||
GetPubKey() crypto.PubKey // validation pubkey
|
||||
GetPower() Rat // validation power
|
||||
GetTokens() Rat // validation tokens
|
||||
GetDelegatorShares() Rat // Total out standing delegator shares
|
||||
GetPower() Dec // validation power
|
||||
GetTokens() Dec // validation tokens
|
||||
GetDelegatorShares() Dec // Total out standing delegator shares
|
||||
GetBondHeight() int64 // height in which the validator became active
|
||||
}
|
||||
|
||||
@@ -68,10 +68,10 @@ type ValidatorSet interface {
|
||||
|
||||
Validator(Context, AccAddress) Validator // get a particular validator by owner AccAddress
|
||||
ValidatorByPubKey(Context, crypto.PubKey) Validator // get a particular validator by signing PubKey
|
||||
TotalPower(Context) Rat // total power of the validator set
|
||||
TotalPower(Context) Dec // total power of the validator set
|
||||
|
||||
// slash the validator and delegators of the validator, specifying offence height, offence power, and slash fraction
|
||||
Slash(Context, crypto.PubKey, int64, int64, Rat)
|
||||
Slash(Context, crypto.PubKey, int64, int64, Dec)
|
||||
Revoke(Context, crypto.PubKey) // revoke a validator
|
||||
Unrevoke(Context, crypto.PubKey) // unrevoke a validator
|
||||
}
|
||||
@@ -82,7 +82,7 @@ type ValidatorSet interface {
|
||||
type Delegation interface {
|
||||
GetDelegator() AccAddress // delegator AccAddress for the bond
|
||||
GetValidator() AccAddress // validator owner AccAddress for the bond
|
||||
GetBondShares() Rat // amount of validator's shares
|
||||
GetBondShares() Dec // amount of validator's shares
|
||||
}
|
||||
|
||||
// properties for the set of all delegations for a particular
|
||||
|
||||
Reference in New Issue
Block a user