2018-02-20 03:31:37 -08:00
|
|
|
package types
|
|
|
|
|
|
|
|
import (
|
2018-03-26 07:53:55 -07:00
|
|
|
"fmt"
|
2018-02-20 03:31:37 -08:00
|
|
|
"math/big"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
2018-05-12 11:33:55 -07:00
|
|
|
"testing"
|
2018-02-20 03:31:37 -08:00
|
|
|
)
|
|
|
|
|
2018-02-20 07:55:53 -08:00
|
|
|
// "that's one big rat!"
|
|
|
|
// ______
|
|
|
|
// / / /\ \____oo
|
|
|
|
// __ /___...._____ _\o
|
|
|
|
// __| |_ |_
|
|
|
|
|
2018-03-16 12:52:39 -07:00
|
|
|
// NOTE: never use new(Rat) or else
|
|
|
|
// we will panic unmarshalling into the
|
|
|
|
// nil embedded big.Rat
|
2018-02-20 03:31:37 -08:00
|
|
|
type Rat struct {
|
2018-04-30 14:21:14 -07:00
|
|
|
big.Rat `json:"rat"`
|
2018-02-20 03:31:37 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// nolint - common values
|
2018-04-30 14:21:14 -07:00
|
|
|
func ZeroRat() Rat { return Rat{*big.NewRat(0, 1)} }
|
|
|
|
func OneRat() Rat { return Rat{*big.NewRat(1, 1)} }
|
2018-02-20 03:31:37 -08:00
|
|
|
|
|
|
|
// New - create a new Rat from integers
|
2018-04-30 14:21:14 -07:00
|
|
|
func NewRat(Numerator int64, Denominator ...int64) Rat {
|
|
|
|
switch len(Denominator) {
|
2018-02-20 03:31:37 -08:00
|
|
|
case 0:
|
2018-04-30 14:21:14 -07:00
|
|
|
return Rat{*big.NewRat(Numerator, 1)}
|
2018-02-20 03:31:37 -08:00
|
|
|
case 1:
|
2018-04-30 14:21:14 -07:00
|
|
|
return Rat{*big.NewRat(Numerator, Denominator[0])}
|
2018-02-20 03:31:37 -08:00
|
|
|
default:
|
|
|
|
panic("improper use of New, can only have one denominator")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-26 07:53:55 -07:00
|
|
|
// create a rational from decimal string or integer string
|
2018-03-14 11:42:50 -07:00
|
|
|
func NewRatFromDecimal(decimalStr string) (f Rat, err Error) {
|
2018-02-20 03:31:37 -08:00
|
|
|
|
|
|
|
// first extract any negative symbol
|
|
|
|
neg := false
|
|
|
|
if string(decimalStr[0]) == "-" {
|
|
|
|
neg = true
|
|
|
|
decimalStr = decimalStr[1:]
|
|
|
|
}
|
|
|
|
|
|
|
|
str := strings.Split(decimalStr, ".")
|
|
|
|
|
|
|
|
var numStr string
|
|
|
|
var denom int64 = 1
|
|
|
|
switch len(str) {
|
|
|
|
case 1:
|
|
|
|
if len(str[0]) == 0 {
|
2018-04-17 19:16:21 -07:00
|
|
|
return f, ErrUnknownRequest("not a decimal string")
|
2018-02-20 03:31:37 -08:00
|
|
|
}
|
|
|
|
numStr = str[0]
|
|
|
|
case 2:
|
|
|
|
if len(str[0]) == 0 || len(str[1]) == 0 {
|
2018-04-17 19:16:21 -07:00
|
|
|
return f, ErrUnknownRequest("not a decimal string")
|
2018-02-20 03:31:37 -08:00
|
|
|
}
|
|
|
|
numStr = str[0] + str[1]
|
|
|
|
len := int64(len(str[1]))
|
|
|
|
denom = new(big.Int).Exp(big.NewInt(10), big.NewInt(len), nil).Int64()
|
|
|
|
default:
|
2018-04-17 19:16:21 -07:00
|
|
|
return f, ErrUnknownRequest("not a decimal string")
|
2018-02-20 03:31:37 -08:00
|
|
|
}
|
|
|
|
|
2018-03-14 11:42:50 -07:00
|
|
|
num, errConv := strconv.Atoi(numStr)
|
|
|
|
if errConv != nil {
|
2018-04-17 19:16:21 -07:00
|
|
|
return f, ErrUnknownRequest(errConv.Error())
|
2018-02-20 03:31:37 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
if neg {
|
|
|
|
num *= -1
|
|
|
|
}
|
|
|
|
|
2018-03-21 14:52:17 -07:00
|
|
|
return NewRat(int64(num), denom), nil
|
2018-02-20 03:31:37 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
//nolint
|
2018-05-12 11:33:55 -07:00
|
|
|
func (r Rat) Num() int64 { return r.Rat.Num().Int64() } // Num - return the numerator
|
|
|
|
func (r Rat) Denom() int64 { return r.Rat.Denom().Int64() } // Denom - return the denominator
|
|
|
|
func (r Rat) IsZero() bool { return r.Num() == 0 } // 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) LT(r2 Rat) bool { return (&(r.Rat)).Cmp(&(r2.Rat)) == -1 } // less than
|
2018-04-30 14:21:14 -07:00
|
|
|
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
|
2018-05-12 11:33:55 -07:00
|
|
|
func (r Rat) String() string { return fmt.Sprintf("%v/%v", r.Num(), r.Denom()) }
|
2018-02-20 03:31:37 -08:00
|
|
|
|
2018-03-26 07:53:55 -07:00
|
|
|
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)
|
|
|
|
)
|
2018-02-20 03:31:37 -08:00
|
|
|
|
2018-03-26 07:53:55 -07:00
|
|
|
// evaluate the rational using bankers rounding
|
2018-02-20 03:31:37 -08:00
|
|
|
func (r Rat) EvaluateBig() *big.Int {
|
|
|
|
|
2018-04-30 14:21:14 -07:00
|
|
|
num := r.Rat.Num()
|
|
|
|
denom := r.Rat.Denom()
|
2018-02-20 03:31:37 -08:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2018-03-26 07:53:55 -07:00
|
|
|
// evaluate the rational using bankers rounding
|
2018-02-20 03:31:37 -08:00
|
|
|
func (r Rat) Evaluate() int64 {
|
|
|
|
return r.EvaluateBig().Int64()
|
|
|
|
}
|
|
|
|
|
2018-03-26 07:53:55 -07:00
|
|
|
// round Rat with the provided precisionFactor
|
2018-03-21 10:52:17 -07:00
|
|
|
func (r Rat) Round(precisionFactor int64) Rat {
|
2018-04-30 14:21:14 -07:00
|
|
|
rTen := Rat{*new(big.Rat).Mul(&(r.Rat), big.NewRat(precisionFactor, 1))}
|
|
|
|
return Rat{*big.NewRat(rTen.Evaluate(), precisionFactor)}
|
2018-02-20 03:31:37 -08:00
|
|
|
}
|
|
|
|
|
2018-03-26 07:53:55 -07:00
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
2018-02-20 03:31:37 -08:00
|
|
|
//___________________________________________________________________________________
|
|
|
|
|
2018-05-01 11:31:56 -07:00
|
|
|
//Wraps r.MarshalText().
|
|
|
|
func (r Rat) MarshalAmino() (string, error) {
|
2018-04-30 14:21:14 -07:00
|
|
|
bz, err := (&(r.Rat)).MarshalText()
|
2018-05-01 11:31:56 -07:00
|
|
|
return string(bz), err
|
2018-04-30 14:21:14 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Requires a valid JSON string - strings quotes and calls UnmarshalText
|
2018-05-01 11:31:56 -07:00
|
|
|
func (r *Rat) UnmarshalAmino(text string) (err error) {
|
2018-04-30 14:21:14 -07:00
|
|
|
tempRat := big.NewRat(0, 1)
|
2018-05-01 11:31:56 -07:00
|
|
|
err = tempRat.UnmarshalText([]byte(text))
|
2018-04-30 14:21:14 -07:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
r.Rat = *tempRat
|
|
|
|
return nil
|
|
|
|
}
|
2018-05-04 12:38:25 -07:00
|
|
|
|
|
|
|
//___________________________________________________________________________________
|
2018-05-12 11:33:55 -07:00
|
|
|
// helpers
|
2018-05-04 12:38:25 -07:00
|
|
|
|
|
|
|
// test if two rat arrays are the 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
|
|
|
|
}
|
2018-05-12 11:33:55 -07:00
|
|
|
|
|
|
|
// intended to be used with require/assert: require.True(RatEq(...))
|
|
|
|
func RatEq(t *testing.T, exp, got Rat) (*testing.T, bool, string, Rat, Rat) {
|
2018-05-12 12:45:40 -07:00
|
|
|
return t, exp.Equal(got), "expected:\t%v\ngot:\t\t%v", exp, got
|
2018-05-12 11:33:55 -07:00
|
|
|
}
|