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-06-26 18:10:34 -07:00
* big . Rat ` json:"rat" `
2018-02-20 03:31:37 -08:00
}
// nolint - common values
2018-06-26 18:10:34 -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-06-26 18:10:34 -07:00
return Rat { big . NewRat ( Numerator , 1 ) }
2018-02-20 03:31:37 -08:00
case 1 :
2018-06-26 18:10:34 -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-07-09 16:08:35 -07:00
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" )
}
}
2018-03-26 07:53:55 -07:00
// create a rational from decimal string or integer string
2018-06-29 13:30:12 -07:00
// precision is the number of values after the decimal point which should be read
func NewRatFromDecimal ( decimalStr string , prec int ) ( f Rat , err Error ) {
2018-02-20 03:31:37 -08:00
// first extract any negative symbol
2018-07-03 20:29:05 -07:00
if len ( decimalStr ) == 0 {
return f , ErrUnknownRequest ( "decimal string is empty" )
}
2018-02-20 03:31:37 -08:00
neg := false
if string ( decimalStr [ 0 ] ) == "-" {
neg = true
decimalStr = decimalStr [ 1 : ]
}
str := strings . Split ( decimalStr , "." )
2018-07-09 16:08:35 -07:00
numStr , denom , err := getNumeratorDenominator ( str , prec )
if err != nil {
return f , err
2018-02-20 03:31:37 -08:00
}
2018-03-14 11:42:50 -07:00
num , errConv := strconv . Atoi ( numStr )
2018-06-29 13:30:12 -07:00
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" )
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
}
2018-06-15 14:16:45 -07:00
// NewRatFromBigInt constructs Rat from big.Int
func NewRatFromBigInt ( num * big . Int , denom ... * big . Int ) Rat {
switch len ( denom ) {
case 0 :
2018-06-26 18:10:34 -07:00
return Rat { new ( big . Rat ) . SetInt ( num ) }
2018-06-15 14:16:45 -07:00
case 1 :
2018-06-26 18:10:34 -07:00
return Rat { new ( big . Rat ) . SetFrac ( num , denom [ 0 ] ) }
2018-06-15 14:16:45 -07:00
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 :
2018-06-26 18:10:34 -07:00
return Rat { new ( big . Rat ) . SetInt ( num . BigInt ( ) ) }
2018-06-15 14:16:45 -07:00
case 1 :
2018-06-26 18:10:34 -07:00
return Rat { new ( big . Rat ) . SetFrac ( num . BigInt ( ) , denom [ 0 ] . BigInt ( ) ) }
2018-06-15 14:16:45 -07:00
default :
panic ( "improper use of NewRatFromBigInt, can only have one denominator" )
}
}
2018-02-20 03:31:37 -08:00
//nolint
2018-06-29 13:30:12 -07:00
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
2018-06-26 18:10:34 -07:00
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
2018-06-26 19:00:12 -07:00
func ( r Rat ) GTE ( r2 Rat ) bool { return ! r . LT ( r2 ) } // greater than or equal
2018-06-26 18:10:34 -07:00
func ( r Rat ) LT ( r2 Rat ) bool { return ( r . Rat ) . Cmp ( r2 . Rat ) == - 1 } // less than
2018-06-26 19:00:12 -07:00
func ( r Rat ) LTE ( r2 Rat ) bool { return ! r . GT ( r2 ) } // less than or equal
2018-06-26 18:10:34 -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
func ( r Rat ) String ( ) string { return r . Rat . String ( ) }
2018-06-26 10:26:12 -07:00
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.
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-07-02 08:57:33 -07:00
// RoundInt64 rounds the rational using bankers rounding
func ( r Rat ) RoundInt64 ( ) int64 {
2018-02-20 03:31:37 -08:00
return r . EvaluateBig ( ) . Int64 ( )
}
2018-07-02 08:57:33 -07:00
// RoundInt round the rational using bankers rounding
func ( r Rat ) RoundInt ( ) Int {
2018-06-15 14:16:45 -07:00
return NewIntFromBigInt ( r . EvaluateBig ( ) )
}
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-06-26 18:10:34 -07:00
rTen := Rat { new ( big . Rat ) . Mul ( r . Rat , big . NewRat ( precisionFactor , 1 ) ) }
2018-07-02 08:57:33 -07:00
return Rat { big . NewRat ( rTen . RoundInt64 ( ) , 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-06-26 18:10:34 -07:00
if r . Rat == nil {
r . Rat = new ( big . Rat )
}
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
}
2018-06-26 18:10:34 -07:00
r . Rat = tempRat
2018-04-30 14:21:14 -07:00
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
2018-07-16 11:42:28 -07:00
// test if two rat arrays are equal
2018-05-04 12:38:25 -07:00
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
}
2018-07-13 13:46:14 -07:00
// minimum rational between two
func MinRat ( r1 , r2 Rat ) Rat {
if r1 . LT ( r2 ) {
return r1
}
return r2
}