import bigInteger from "big-integer" ; import { Int } from "./int" ; import { CoinUtils } from "./coin-utils" ; import { exponentDecStringToDecString , isExponentDecString , isValidDecimalString , } from "./etc" ; export class Dec { public static readonly precision = 18 ; protected static readonly decimalPrecisionBits = 60 ; protected static readonly maxDec = bigInteger ( "133499189745056880149688856635597007162669032647290798121690100488888732861290034376435130433535" ) ; protected static readonly precisionMultipliers : { [ key ] : bigInteger . BigInteger | undefined ; } = { } ; protected static calcPrecisionMultiplier ( prec ) { if ( prec < 0 ) { throw new Error ( "Invalid prec" ) ; } if ( prec > Dec . precision ) { throw new Error ( "Too much precision" ) ; } if ( Dec . precisionMultipliers [ prec . toString ( ) ] ) { return Dec . precisionMultipliers [ prec . toString ( ) ] ! ; } const zerosToAdd = Dec . precision - prec ; const multiplier = bigInteger ( 10 ) . pow ( zerosToAdd ) ; Dec . precisionMultipliers [ prec . toString ( ) ] = multiplier ; return multiplier ; } protected static reduceDecimalsFromString ( str ) { const decimalPointIndex = str . indexOf ( "." ) ; if ( decimalPointIndex < 0 ) { return { res : str , isDownToZero : false , } ; } const exceededDecimals = str . length - 1 - decimalPointIndex - Dec . precision ; if ( exceededDecimals <= 0 ) { return { res : str , isDownToZero : false , } ; } const res = str . slice ( 0 , str . length - exceededDecimals ) ; return { res , isDownToZero : /^[0.]*$/ . test ( res ) , } ; } protected int : bigInteger . BigInteger ; param int param prec constructor ( int , prec = 0 ) { if ( typeof int === "number" ) { int = int . toString ( ) ; } if ( typeof int === "string" ) { if ( int . length === 0 ) { throw new Error ( "empty string" ) ; } if ( ! isValidDecimalString ( int ) ) { if ( isExponentDecString ( int ) ) { int = exponentDecStringToDecString ( int ) ; } else { throw new Error ( `invalid decimal: ${ int }` ) ; } } const reduced = Dec . reduceDecimalsFromString ( int ) ; if ( reduced . isDownToZero ) { console . log ( `WARNING: Got ${ int }. Dec can only handle up to 18 decimals. However, since the decimal point of the input exceeds 18 digits, the remainder is discarded. As a result, input becomes 0.` ) ; } int = reduced . res ; if ( int . indexOf ( "." ) >= 0 ) { prec = int . length - int . indexOf ( "." ) - 1 ; int = int . replace ( "." , "" ) ; } this . int = bigInteger ( int ) ; } else if ( int instanceof Int ) { this . int = bigInteger ( int . toString ( ) ) ; } else if ( typeof int === "bigint" ) { this . int = bigInteger ( int ) ; } else { this . int = bigInteger ( int ) ; } this . int = this . int . multiply ( Dec . calcPrecisionMultiplier ( prec ) ) ; this . checkBitLen ( ) ; } protected checkBitLen ( ) { if ( this . int . abs ( ) . gt ( Dec . maxDec ) ) { throw new Error ( `Integer out of range ${ this . int . toString ( ) }` ) ; } } public isZero ( ) { return this . int . eq ( bigInteger ( 0 ) ) ; } public isNegative ( ) { return this . int . isNegative ( ) ; } public isPositive ( ) { return this . int . isPositive ( ) ; } public equals ( d2 ) { return this . int . eq ( d2 . int ) ; } /**
     * Alias for the greater method.
     */ public gt ( d2 ) { return this . int . gt ( d2 . int ) ; } /**
     * Alias for the greaterOrEquals method.
     */ public gte ( d2 ) { return this . int . geq ( d2 . int ) ; } /**
     * Alias for the lesser method.
     */ public lt ( d2 ) { return this . int . lt ( d2 . int ) ; } /**
     * Alias for the lesserOrEquals method.
     */ public lte ( d2 ) { return this . int . leq ( d2 . int ) ; } /**
     * reverse the decimal sign.
     */ public neg ( ) { return new Dec ( this . int . negate ( ) , Dec . precision ) ; } /**
     * Returns the absolute value of a decimals.
     */ public abs ( ) { return new Dec ( this . int . abs ( ) , Dec . precision ) ; } public add ( d2 ) { return new Dec ( this . int . add ( d2 . int ) , Dec . precision ) ; } public sub ( d2 ) { return new Dec ( this . int . subtract ( d2 . int ) , Dec . precision ) ; } public pow ( n ) { if ( n . isZero ( ) ) { return new Dec ( 1 ) ; } if ( n . isNegative ( ) ) { return new Dec ( 1 ) . quo ( this . pow ( n . abs ( ) ) ) ; } let base = new Dec ( this . int , Dec . precision ) ; let tmp = new Dec ( 1 ) ; for ( let i = n ; i . gt ( new Int ( 1 ) ) ; i = i . div ( new Int ( 2 ) ) ) { if ( ! i . mod ( new Int ( 2 ) ) . isZero ( ) ) { tmp = tmp . mul ( base ) ; } base = base . mul ( base ) ; } return base . mul ( tmp ) ; } public mul ( d2 ) { return new Dec ( this . mulRaw ( d2 ) . chopPrecisionAndRound ( ) , Dec . precision ) ; } public mulTruncate ( d2 ) { return new Dec ( this . mulRaw ( d2 ) . chopPrecisionAndTruncate ( ) , Dec . precision ) ; } protected mulRaw ( d2 ) { return new Dec ( this . int . multiply ( d2 . int ) , Dec . precision ) ; } public quo ( d2 ) { return new Dec ( this . quoRaw ( d2 ) . chopPrecisionAndRound ( ) , Dec . precision ) ; } public quoTruncate ( d2 ) { return new Dec ( this . quoRaw ( d2 ) . chopPrecisionAndTruncate ( ) , Dec . precision ) ; } public quoRoundUp ( d2 ) { return new Dec ( this . quoRaw ( d2 ) . chopPrecisionAndRoundUp ( ) , Dec . precision ) ; } protected quoRaw ( d2 ) { const precision = Dec . calcPrecisionMultiplier ( 0 ) ; const mul = this . int . multiply ( precision ) . multiply ( precision ) ; return new Dec ( mul . divide ( d2 . int ) , Dec . precision ) ; } public isInteger ( ) { const precision = Dec . calcPrecisionMultiplier ( 0 ) ; return this . int . remainder ( precision ) . equals ( bigInteger ( 0 ) ) ; } /**
     * Remove a Precision amount of rightmost digits and perform bankers rounding
     * on the remainder (gaussian rounding) on the digits which have been removed.
     */ protected chopPrecisionAndRound ( ) { if ( this . isNegative ( ) ) { const absoulteDec = this . abs ( ) ; const choped = absoulteDec . chopPrecisionAndRound ( ) ; return choped . negate ( ) ; } const precision = Dec . calcPrecisionMultiplier ( 0 ) ; const fivePrecision = precision . divide ( bigInteger ( 2 ) ) ; const { quotient , remainder } = this . int . divmod ( precision ) ; if ( remainder . equals ( bigInteger ( 0 ) ) ) { return quotient ; } if ( remainder . lt ( fivePrecision ) ) { return quotient ; } else if ( remainder . gt ( fivePrecision ) ) { return quotient . add ( bigInteger ( 1 ) ) ; } else { if ( quotient . divide ( bigInteger ( 2 ) ) . equals ( bigInteger ( 0 ) ) ) { return quotient ; } else { return quotient . add ( bigInteger ( 1 ) ) ; } } } protected chopPrecisionAndRoundUp ( ) { if ( this . isNegative ( ) ) { const absoulteDec = this . abs ( ) ; const choped = absoulteDec . chopPrecisionAndTruncate ( ) ; return choped . negate ( ) ; } const precision = Dec . calcPrecisionMultiplier ( 0 ) ; const { quotient , remainder } = this . int . divmod ( precision ) ; if ( remainder . equals ( bigInteger ( 0 ) ) ) { return quotient ; } return quotient . add ( bigInteger ( 1 ) ) ; } /**
     * Similar to chopPrecisionAndRound, but always rounds down
     */ protected chopPrecisionAndTruncate ( ) { const precision = Dec . calcPrecisionMultiplier ( 0 ) ; return this . int . divide ( precision ) ; } public toString ( prec = Dec . precision , locale = false ) { const precision = Dec . calcPrecisionMultiplier ( 0 ) ; const int = this . int . abs ( ) ; const { quotient : integer , remainder : fraction } = int . divmod ( precision ) ; let fractionStr = fraction . toString ( 10 ) ; for ( let i = 0 , l = fractionStr . length ; i < Dec . precision - l ; i ++ ) { fractionStr = "0" + fractionStr ; } fractionStr = fractionStr . substring ( 0 , prec ) ; const isNegative = this . isNegative ( ) && ! ( integer . eq ( bigInteger ( 0 ) ) && fractionStr . length === 0 ) ; const integerStr = locale ? CoinUtils . integerStringToUSLocaleString ( integer . toString ( ) ) : integer . toString ( ) ; return `${ isNegative ? "-" : "" }${ integerStr }${ fractionStr . length > 0 ? "." + fractionStr : "" }` ; } public round ( ) { return new Int ( this . chopPrecisionAndRound ( ) ) ; } public roundUp ( ) { return new Int ( this . chopPrecisionAndRoundUp ( ) ) ; } public truncate ( ) { return new Int ( this . chopPrecisionAndTruncate ( ) ) ; } public roundDec ( ) { return new Dec ( this . chopPrecisionAndRound ( ) , 0 ) ; } public roundUpDec ( ) { return new Dec ( this . chopPrecisionAndRoundUp ( ) , 0 ) ; } public truncateDec ( ) { return new Dec ( this . chopPrecisionAndTruncate ( ) , 0 ) ; } }