// Package decimal implements an arbitrary precision fixed-point decimal. // // The zero-value of a Decimal is 0, as you would expect. // // The best way to create a new Decimal is to use decimal.NewFromString, ex: // // n, err := decimal.NewFromString("-123.4567") // n.String() // output: "-123.4567" // // To use Decimal as part of a struct: // // type StructName struct { // Number Decimal // } // // Note: This can "only" represent numbers with a maximum of 2^31 digits after the decimal point. package decimal import ( "fmt" "math" "math/big" "math/bits" "regexp" "strconv" "strings" ) // DivisionPrecision is the number of decimal places in the result when it // doesn't divide exactly. // // Example: // // d1 := decimal.NewFromFloat(2).Div(decimal.NewFromFloat(3)) // d1.String() // output: "0.6666666666666667" // d2 := decimal.NewFromFloat(2).Div(decimal.NewFromFloat(30000)) // d2.String() // output: "0.0000666666666667" // d3 := decimal.NewFromFloat(20000).Div(decimal.NewFromFloat(3)) // d3.String() // output: "6666.6666666666666667" // decimal.DivisionPrecision = 3 // d4 := decimal.NewFromFloat(2).Div(decimal.NewFromFloat(3)) // d4.String() // output: "0.667" var DivisionPrecision = 16 // PowPrecisionNegativeExponent specifies the number of digits after the decimal point in the results of // Pow, PowInt32 and PowBigInt for negative or non-integer exponents. A result that would round to 0 keeps // PowPrecisionNegativeExponent significant digits instead (at least one). // PowWithPrecision is not affected, it takes the precision as an argument. // // Example: // // d1, err := decimal.NewFromFloat(15.2).PowInt32(-2) // d1.String() // output: "0.0043282548476454" // // decimal.PowPrecisionNegativeExponent = 24 // d2, err := decimal.NewFromFloat(15.2).PowInt32(-2) // d2.String() // output: "0.004328254847645429362881" var PowPrecisionNegativeExponent = 16 // MarshalJSONWithoutQuotes should be set to true if you want the decimal to // be JSON marshaled as a number, instead of as a string. // WARNING: this is dangerous for decimals with many digits, since many JSON // unmarshallers (ex: Javascript's) will unmarshal JSON numbers to IEEE 754 // double-precision floating point numbers, which means you can potentially // silently lose precision. var MarshalJSONWithoutQuotes = false // TrimTrailingZeros specifies whether trailing zeroes should be trimmed from a string representation of decimal. // If set to true, trailing zeroes will be truncated (2.00 -> 2, 3.11 -> 3.11, 13.000 -> 13), // otherwise trailing zeroes will be preserved (2.00 -> 2.00, 3.11 -> 3.11, 13.000 -> 13.000). // Setting this value to false can be useful for APIs where exact decimal string representation matters. var TrimTrailingZeros = true // UseScientificNotation specifies whether scientific notation should be used when a decimal is turned // into a string that has a "negative" precision. // // For example, 1200 rounded to the nearest 100 cannot accurately be shown as "1200" because the last two // digits are unknown. With this set to true, that number would be expressed as "1.2E3" instead. var UseScientificNotation = false // ExpMaxIterations specifies the maximum number of iterations needed to calculate // precise natural exponent value using ExpHullAbrham method. var ExpMaxIterations = 1000 // MaxDecodeExponent is the largest exponent magnitude that NewFromString and UnmarshalBinary accept, // and so UnmarshalJSON, UnmarshalText, Scan, GobDecode, DecodeSpanner and their NullDecimal variants too. // Operations like String, Add and Float64 take time and memory that grow with the exponent, so without // this limit a short input like "1e-2000000000" can stall the process or exhaust its memory. // The default covers the float64 and IEEE 754 decimal128 exponent ranges. // // Values created with New, NewFromBigInt or arithmetic are not checked, so they may not decode back from // their own String or MarshalBinary output. The limit does not bound the cost of Pow or ExpTaylor // arguments or of very long inputs; validate those separately. // // Set it once at program start, before any decoding. Raise it only for trusted input. var MaxDecodeExponent = 10000 // Zero constant, to make computations faster. // Zero should never be compared with == or != directly, please use decimal.Equal or decimal.Cmp instead. var Zero = Decimal{} // Decimal represents a fixed-point decimal. It is immutable. // number = value * 10 ^ exp type Decimal struct { value *big.Int // NOTE(vadim): this must be an int32, because we cast it to float64 during // calculations. If exp is 64 bit, we might lose precision. // If we cared about being able to represent every possible decimal, we // could make exp a *big.Int but it would hurt performance and numbers // like that are unrealistic. exp int32 } func (d Decimal) getValue() *big.Int { if d.value == nil { return zeroInt } return d.value } // New returns a new fixed-point decimal, value * 10 ^ exp. func New(value int64, exp int32) Decimal { return Decimal{ value: big.NewInt(value), exp: exp, } } // NewFromInt converts an int64 to Decimal. // // Example: // // NewFromInt(123).String() // output: "123" // NewFromInt(-10).String() // output: "-10" func NewFromInt(value int64) Decimal { return Decimal{ value: big.NewInt(value), exp: 0, } } // NewFromInt32 converts an int32 to Decimal. // // Example: // // NewFromInt(123).String() // output: "123" // NewFromInt(-10).String() // output: "-10" func NewFromInt32(value int32) Decimal { return Decimal{ value: big.NewInt(int64(value)), exp: 0, } } // NewFromUint64 converts an uint64 to Decimal. // // Example: // // NewFromUint64(123).String() // output: "123" func NewFromUint64(value uint64) Decimal { return Decimal{ value: new(big.Int).SetUint64(value), exp: 0, } } // NewFromBigInt returns a new Decimal from a big.Int, value * 10 ^ exp func NewFromBigInt(value *big.Int, exp int32) Decimal { return Decimal{ value: new(big.Int).Set(value), exp: exp, } } // NewFromBigRat returns a new Decimal from a big.Rat. The numerator and // denominator are divided and rounded to the given precision. // // Example: // // d1 := NewFromBigRat(big.NewRat(0, 1), 0) // output: "0" // d2 := NewFromBigRat(big.NewRat(4, 5), 1) // output: "0.8" // d3 := NewFromBigRat(big.NewRat(1000, 3), 3) // output: "333.333" // d4 := NewFromBigRat(big.NewRat(2, 7), 4) // output: "0.2857" func NewFromBigRat(value *big.Rat, precision int32) Decimal { return Decimal{ value: new(big.Int).Set(value.Num()), exp: 0, }.DivRound(Decimal{ value: new(big.Int).Set(value.Denom()), exp: 0, }, precision) } // NewFromString returns a new Decimal from a string representation. // Trailing zeroes are not trimmed. // // Example: // // d, err := NewFromString("-123.45") // d2, err := NewFromString(".0001") // d3, err := NewFromString("1.47000") func NewFromString(value string) (Decimal, error) { originalInput := value var exp int64 // Check if number is using scientific notation and find dots eIndex := -1 pIndex := -1 for i, r := range value { if r == 'E' || r == 'e' { if eIndex > -1 { return Decimal{}, fmt.Errorf("can't convert %s to decimal: multiple 'E' characters found", value) } eIndex = i continue } if r == '.' { if pIndex > -1 { return Decimal{}, fmt.Errorf("can't convert %s to decimal: too many .s", value) } pIndex = i } } if eIndex != -1 { expInt, err := strconv.ParseInt(value[eIndex+1:], 10, 32) if err != nil { if e, ok := err.(*strconv.NumError); ok && e.Err == strconv.ErrRange { return Decimal{}, fmt.Errorf("can't convert %s to decimal: fractional part too long", value) } return Decimal{}, fmt.Errorf("can't convert %s to decimal: exponent is not numeric", value) } value = value[:eIndex] exp = expInt } numLen := len(value) if pIndex != -1 { if pIndex+1 < len(value) && (value[pIndex+1] == '-' || value[pIndex+1] == '+') { // ParseInt and SetString would accept the sign once the point is removed, as in ".-5" return Decimal{}, fmt.Errorf("can't convert %s to decimal", value) } numLen-- expInt := -len(value[pIndex+1:]) exp += int64(expInt) } var dValue *big.Int // parsing in an int64 is faster than new(big.Int).SetString so this is just a shortcut for strings we know won't overflow if numLen <= 18 { parsed64, ok := parseInt64SkipIndex(value, pIndex) if !ok { return Decimal{}, fmt.Errorf("can't convert %s to decimal", value) } dValue = big.NewInt(parsed64) } else { intString := value if pIndex != -1 { intString = value[:pIndex] + value[pIndex+1:] } dValue = new(big.Int) _, ok := dValue.SetString(intString, 10) if !ok { return Decimal{}, fmt.Errorf("can't convert %s to decimal", value) } } if exp < math.MinInt32 || exp > math.MaxInt32 { // NOTE(vadim): I doubt a string could realistically be this long return Decimal{}, fmt.Errorf("can't convert %s to decimal: fractional part too long", originalInput) } if exp > int64(MaxDecodeExponent) || exp < -int64(MaxDecodeExponent) { return Decimal{}, fmt.Errorf("can't convert %s to decimal: exponent %d exceeds MaxDecodeExponent (%d)", originalInput, exp, MaxDecodeExponent) } return Decimal{ value: dValue, exp: int32(exp), }, nil } // parseInt64SkipIndex parses s without the byte at index skip as strconv.ParseInt(s, 10, 64) would, // s must have at most 18 other bytes so that the result can't overflow. func parseInt64SkipIndex(s string, skip int) (int64, bool) { i, neg := 0, false if len(s) > 0 && skip != 0 && (s[0] == '+' || s[0] == '-') { i, neg = 1, s[0] == '-' } var n int64 digits := 0 for ; i < len(s); i++ { if i == skip { continue } c := s[i] if c < '0' || c > '9' { return 0, false } n = n*10 + int64(c-'0') digits++ } if neg { n = -n } return n, digits > 0 } // NewFromFormattedString returns a new Decimal from a formatted string representation. // The second argument - replRegexp, is a regular expression that is used to find characters that should be // removed from given decimal string representation. All matched characters will be replaced with an empty string. // // Example: // // r := regexp.MustCompile("[$,]") // d1, err := NewFromFormattedString("$5,125.99", r) // // r2 := regexp.MustCompile("[_]") // d2, err := NewFromFormattedString("1_000_000", r2) // // r3 := regexp.MustCompile("[USD\\s]") // d3, err := NewFromFormattedString("5000 USD", r3) func NewFromFormattedString(value string, replRegexp *regexp.Regexp) (Decimal, error) { parsedValue := replRegexp.ReplaceAllString(value, "") d, err := NewFromString(parsedValue) if err != nil { return Decimal{}, err } return d, nil } // RequireFromString returns a new Decimal from a string representation // or panics if NewFromString had returned an error. // // Example: // // d := RequireFromString("-123.45") // d2 := RequireFromString(".0001") func RequireFromString(value string) Decimal { dec, err := NewFromString(value) if err != nil { panic(err) } return dec } // NewFromFloat converts a float64 to Decimal. // // The result is the shortest decimal that converts back to the same float64, // the same number that strconv.FormatFloat(value, 'f', -1, 64) prints. // This is typically 15 digits, but may be more in some cases. // See https://www.exploringbinary.com/decimal-precision-of-binary-floating-point-numbers/ for more information. // // This is not always the exact value stored in the float. Whole numbers larger // than 2^53 can change: float64(1<<62) is exactly 4611686018427387904, but // NewFromFloat returns 4611686018427388000. To convert whole numbers exactly, // use NewFromFloatWithExponent(value, 0). // // For slightly faster conversion, use NewFromFloatWithExponent where you can specify the precision in absolute terms. // // NOTE: this will panic on NaN, +/-inf func NewFromFloat(value float64) Decimal { if value == 0 { return New(0, 0) } return newFromFloat(value, math.Float64bits(value), &float64info) } // NewFromFloat32 converts a float32 to Decimal. // // The result is the shortest decimal that converts back to the same float32. // This is typically 6-8 digits depending on the input. // See https://www.exploringbinary.com/decimal-precision-of-binary-floating-point-numbers/ for more information. // // As with NewFromFloat, whole numbers larger than 2^24 can change. To convert // them exactly, use NewFromFloatWithExponent(float64(value), 0). // // For slightly faster conversion, use NewFromFloatWithExponent where you can specify the precision in absolute terms. // // NOTE: this will panic on NaN, +/-inf func NewFromFloat32(value float32) Decimal { if value == 0 { return New(0, 0) } // XOR is workaround for https://github.com/golang/go/issues/26285 a := math.Float32bits(value) ^ 0x80808080 return newFromFloat(float64(value), uint64(a)^0x80808080, &float32info) } func newFromFloat(val float64, bits uint64, flt *floatInfo) Decimal { if math.IsNaN(val) || math.IsInf(val, 0) { panic(fmt.Sprintf("Cannot create a Decimal from %v", val)) } exp := int(bits>>flt.mantbits) & (1<>(flt.expbits+flt.mantbits) != 0 roundShortest(&d, mant, exp, flt) // If less than 19 digits, we can do calculation in an int64. if d.nd < 19 { tmp := int64(0) m := int64(1) for i := d.nd - 1; i >= 0; i-- { tmp += m * int64(d.d[i]-'0') m *= 10 } if d.neg { tmp *= -1 } return Decimal{value: big.NewInt(tmp), exp: int32(d.dp) - int32(d.nd)} } dValue := new(big.Int) dValue, ok := dValue.SetString(string(d.d[:d.nd]), 10) if ok { return Decimal{value: dValue, exp: int32(d.dp) - int32(d.nd)} } return NewFromFloatWithExponent(val, int32(d.dp)-int32(d.nd)) } // NewFromFloatWithExponent converts a float64 to Decimal, with an arbitrary // number of fractional digits. // // Example: // // NewFromFloatWithExponent(123.456, -2).String() // output: "123.46" func NewFromFloatWithExponent(value float64, exp int32) Decimal { if math.IsNaN(value) || math.IsInf(value, 0) { panic(fmt.Sprintf("Cannot create a Decimal from %v", value)) } bits := math.Float64bits(value) mant := bits & (1<<52 - 1) exp2 := int32((bits >> 52) & (1<<11 - 1)) sign := bits >> 63 if exp2 == 0 { // specials if mant == 0 { return Decimal{} } // subnormal exp2++ } else { // normal mant |= 1 << 52 } exp2 -= 1023 + 52 // normalizing base-2 values for mant&1 == 0 { mant = mant >> 1 exp2++ } // maximum number of fractional base-10 digits to represent 2^N exactly cannot be more than -N if N<0 if exp < 0 && exp < exp2 { if exp2 < 0 { exp = exp2 } else { exp = 0 } } // representing 10^M * 2^N as 5^M * 2^(M+N) exp2 -= exp temp := big.NewInt(1) dMant := big.NewInt(int64(mant)) // applying 5^M if exp > 0 { temp = temp.SetInt64(int64(exp)) temp = temp.Exp(fiveInt, temp, nil) } else if exp < 0 { temp = temp.SetInt64(-int64(exp)) temp = temp.Exp(fiveInt, temp, nil) dMant = dMant.Mul(dMant, temp) temp = temp.SetUint64(1) } // applying 2^(M+N) if exp2 > 0 { dMant = dMant.Lsh(dMant, uint(exp2)) } else if exp2 < 0 { temp = temp.Lsh(temp, uint(-exp2)) } // rounding and downscaling if exp > 0 || exp2 < 0 { halfDown := new(big.Int).Rsh(temp, 1) dMant = dMant.Add(dMant, halfDown) dMant = dMant.Quo(dMant, temp) } if sign == 1 { dMant = dMant.Neg(dMant) } return Decimal{ value: dMant, exp: exp, } } // Copy returns a copy of decimal with the same value and exponent, but a different pointer to value. func (d Decimal) Copy() Decimal { return Decimal{ value: new(big.Int).Set(d.getValue()), exp: d.exp, } } // rescale returns a rescaled version of the decimal. Returned // decimal may be less precise if the given exponent is bigger // than the initial exponent of the Decimal. // NOTE: this will truncate, NOT round // // Example: // // d := New(12345, -4) // d2 := d.rescale(-1) // d3 := d2.rescale(-4) // println(d1) // println(d2) // println(d3) // // Output: // // 1.2345 // 1.2 // 1.2000 func (d Decimal) rescale(exp int32) Decimal { if d.exp == exp { return Decimal{ new(big.Int).Set(d.getValue()), d.exp, } } // NOTE(vadim): must convert exps to float64 before - to prevent overflow diff := math.Abs(float64(exp) - float64(d.exp)) value := new(big.Int) if exp > d.exp { value.Quo(d.getValue(), pow10(int64(diff))) if value.Sign() == 0 && d.getValue().Sign() != 0 { // reflect.DeepEqual tells an empty word slice from nil, keep the empty one that // dividing a copy of d.value in place used to leave value.SetBits([]big.Word{}) } } else { value.Mul(d.getValue(), pow10(int64(diff))) } return Decimal{ value: value, exp: exp, } } // Abs returns the absolute value of the decimal. func (d Decimal) Abs() Decimal { if !d.IsNegative() { return d } d2Value := new(big.Int).Abs(d.getValue()) return Decimal{ value: d2Value, exp: d.exp, } } // Add returns d + d2. func (d Decimal) Add(d2 Decimal) Decimal { // rescale returns a new value, so the sum can be stored in it if d.exp < d2.exp { r := d2.rescale(d.exp) r.value.Add(d.getValue(), r.value) return r } else if d.exp > d2.exp { r := d.rescale(d2.exp) r.value.Add(r.value, d2.getValue()) return r } d3Value := new(big.Int).Add(d.getValue(), d2.getValue()) return Decimal{ value: d3Value, exp: d.exp, } } // Sub returns d - d2. func (d Decimal) Sub(d2 Decimal) Decimal { // rescale returns a new value, so the difference can be stored in it if d.exp < d2.exp { r := d2.rescale(d.exp) r.value.Sub(d.getValue(), r.value) return r } else if d.exp > d2.exp { r := d.rescale(d2.exp) r.value.Sub(r.value, d2.getValue()) return r } d3Value := new(big.Int).Sub(d.getValue(), d2.getValue()) return Decimal{ value: d3Value, exp: d.exp, } } // Neg returns -d. func (d Decimal) Neg() Decimal { val := new(big.Int).Neg(d.getValue()) return Decimal{ value: val, exp: d.exp, } } // Mul returns d * d2. func (d Decimal) Mul(d2 Decimal) Decimal { expInt64 := int64(d.exp) + int64(d2.exp) if expInt64 > math.MaxInt32 || expInt64 < math.MinInt32 { // NOTE(vadim): better to panic than give incorrect results, as // Decimals are usually used for money panic(fmt.Sprintf("exponent %v overflows an int32!", expInt64)) } d3Value := new(big.Int).Mul(d.getValue(), d2.getValue()) return Decimal{ value: d3Value, exp: int32(expInt64), } } // Shift shifts the decimal in base 10. // It shifts left when shift is positive and right if shift is negative. // In simpler terms, the given value for shift is added to the exponent // of the decimal. // Shift panics if the resulting exponent does not fit in an int32. func (d Decimal) Shift(shift int32) Decimal { exp := int64(d.exp) + int64(shift) if exp > math.MaxInt32 || exp < math.MinInt32 { panic(fmt.Sprintf("exponent %v overflows an int32!", exp)) } value := d.getValue() if value.Sign() == 0 { // a copy of zero has a nil word slice, which reflect.DeepEqual tells from an empty one value = zeroInt } return Decimal{ value: value, exp: int32(exp), } } // Div returns d / d2. If it doesn't divide exactly, the result will have // DivisionPrecision digits after the decimal point. func (d Decimal) Div(d2 Decimal) Decimal { return d.DivRound(d2, int32(DivisionPrecision)) } // QuoRem does division with remainder // d.QuoRem(d2,precision) returns quotient q and remainder r such that // // d = d2 * q + r, q an integer multiple of 10^(-precision) // 0 <= r < abs(d2) * 10 ^(-precision) if d>=0 // 0 >= r > -abs(d2) * 10 ^(-precision) if d<0 // // Note that precision<0 is allowed as input. func (d Decimal) QuoRem(d2 Decimal, precision int32) (Decimal, Decimal) { return d.quoRem(d2, precision, nil) } // quoRem is QuoRem that also sets bb, if not nil, to the divisor in the units of the remainder. func (d Decimal) quoRem(d2 Decimal, precision int32, bb *big.Int) (Decimal, Decimal) { if d2.getValue().Sign() == 0 { panic("decimal division by 0") } scale := -precision e := int64(d.exp) - int64(d2.exp) - int64(scale) if e > math.MaxInt32 || e < math.MinInt32 { panic("overflow in decimal QuoRem") } if bb == nil { bb = new(big.Int) } var aa big.Int var scalerest int32 // d = a 10^ea // d2 = b 10^eb if e < 0 { aa = *d.getValue() bb.Mul(d2.getValue(), pow10(-e)) scalerest = d.exp // now aa = a // bb = b 10^(scale + eb - ea) } else { aa.Mul(d.getValue(), pow10(e)) *bb = *d2.getValue() scalerest = scale + d2.exp // now aa = a ^ (ea - eb - scale) // bb = b } var q, r big.Int q.QuoRem(&aa, bb, &r) dq := Decimal{value: &q, exp: scale} dr := Decimal{value: &r, exp: scalerest} return dq, dr } // DivRound divides and rounds to a given precision // i.e. to an integer multiple of 10^(-precision) // // for a positive quotient digit 5 is rounded up, away from 0 // if the quotient is negative then digit 5 is rounded down, away from 0 // // Note that precision<0 is allowed as input. func (d Decimal) DivRound(d2 Decimal, precision int32) Decimal { // quoRem already checks initialization var bb big.Int q, r := d.quoRem(d2, precision, &bb) // the actual rounding decision is based on comparing r*10^precision and d2/2, // which is comparing 2*|r.value| and |bb| var rv2 big.Int rv2.Abs(r.getValue()) rv2.Lsh(&rv2, 1) if rv2.CmpAbs(&bb) < 0 { return q } if d.getValue().Sign()*d2.getValue().Sign() < 0 { q.value.Sub(q.value, oneInt) } else { q.value.Add(q.value, oneInt) } return q } // Mod returns d % d2. func (d Decimal) Mod(d2 Decimal) Decimal { _, r := d.QuoRem(d2, 0) return r } // NumDigits returns the number of digits of the decimal coefficient (d.Value) func (d Decimal) NumDigits() int { v := d.getValue() if v.IsInt64() { u := uint64(v.Int64()) if u == 0 { return 1 } if v.Sign() < 0 { u = -u } // Not math.Log10, which rounds an exact 1eN like 1e15 down (see #420). // bits.Len64(u)*1233>>12 is floor(log10(2^bits)), the digit count or one less. n := bits.Len64(u) * 1233 >> 12 if u >= pow10Uint64[n] { n++ } return n } estimatedNumDigits := int(float64(v.BitLen()) / math.Log2(10)) // estimatedNumDigits (lg10) may be off by 1, need to verify if v.CmpAbs(pow10(int64(estimatedNumDigits))) >= 0 { return estimatedNumDigits + 1 } return estimatedNumDigits } // IsInteger returns true when decimal can be represented as an integer value, otherwise, it returns false. func (d Decimal) IsInteger() bool { // The most typical case, all decimal with exponent higher or equal 0 can be represented as integer if d.exp >= 0 { return true } v := d.getValue() if v.Sign() == 0 { return true } if d.exp >= -18 && v.IsInt64() { return v.Int64()%int64(pow10Uint64[-d.exp]) == 0 } // When the exponent is negative we have to check every number after the decimal place // If all of them are zeroes, we are sure that given decimal can be represented as an integer var r big.Int q := new(big.Int).Set(v) for z := abs(d.exp); z > 0; z-- { q.QuoRem(q, tenInt, &r) if r.Cmp(zeroInt) != 0 { return false } } return true } // Abs calculates absolute value of any int32. Used for calculating absolute value of decimal's exponent. func abs(n int32) int32 { if n < 0 { return -n } return n } // Cmp compares the numbers represented by d and d2 and returns: // // -1 if d < d2 // 0 if d == d2 // +1 if d > d2 func (d Decimal) Cmp(d2 Decimal) int { if d.exp == d2.exp { return d.getValue().Cmp(d2.getValue()) } if s, s2 := d.Sign(), d2.Sign(); s != s2 { if s > s2 { return 1 } return -1 } else if s == 0 { return 0 } var scaled big.Int if d.exp < d2.exp { return d.getValue().Cmp(scaled.Mul(d2.getValue(), pow10(int64(d2.exp)-int64(d.exp)))) } return scaled.Mul(d.getValue(), pow10(int64(d.exp)-int64(d2.exp))).Cmp(d2.getValue()) } // Compare compares the numbers represented by d and d2 and returns: // // -1 if d < d2 // 0 if d == d2 // +1 if d > d2 func (d Decimal) Compare(d2 Decimal) int { return d.Cmp(d2) } // Equal returns whether the numbers represented by d and d2 are equal. func (d Decimal) Equal(d2 Decimal) bool { return d.Cmp(d2) == 0 } // Equals returns whether the numbers represented by d and d2 are equal. // // Deprecated: Equals is deprecated, please use Equal method instead. func (d Decimal) Equals(d2 Decimal) bool { return d.Equal(d2) } // GreaterThan (GT) returns true when d is greater than d2. func (d Decimal) GreaterThan(d2 Decimal) bool { return d.Cmp(d2) == 1 } // GreaterThanOrEqual (GTE) returns true when d is greater than or equal to d2. func (d Decimal) GreaterThanOrEqual(d2 Decimal) bool { cmp := d.Cmp(d2) return cmp == 1 || cmp == 0 } // LessThan (LT) returns true when d is less than d2. func (d Decimal) LessThan(d2 Decimal) bool { return d.Cmp(d2) == -1 } // LessThanOrEqual (LTE) returns true when d is less than or equal to d2. func (d Decimal) LessThanOrEqual(d2 Decimal) bool { cmp := d.Cmp(d2) return cmp == -1 || cmp == 0 } // Sign returns: // // -1 if d < 0 // 0 if d == 0 // +1 if d > 0 func (d Decimal) Sign() int { return d.getValue().Sign() } // IsPositive return // // true if d > 0 // false if d == 0 // false if d < 0 func (d Decimal) IsPositive() bool { return d.Sign() == 1 } // IsNegative return // // true if d < 0 // false if d == 0 // false if d > 0 func (d Decimal) IsNegative() bool { return d.Sign() == -1 } // IsZero return // // true if d == 0 // false if d > 0 // false if d < 0 func (d Decimal) IsZero() bool { return d.Sign() == 0 } // Exponent returns the exponent, or scale component of the decimal. func (d Decimal) Exponent() int32 { return d.exp } // Coefficient returns the coefficient of the decimal. It is scaled by 10^Exponent() func (d Decimal) Coefficient() *big.Int { // we copy the coefficient so that mutating the result does not mutate the Decimal. return new(big.Int).Set(d.getValue()) } // CoefficientInt64 returns the coefficient of the decimal as int64. It is scaled by 10^Exponent() // If coefficient cannot be represented in an int64, the result will be undefined. func (d Decimal) CoefficientInt64() int64 { return d.getValue().Int64() } // IntPart returns the integer component of the decimal. func (d Decimal) IntPart() int64 { if v := d.getValue(); d.exp <= 0 && d.exp >= -18 && v.IsInt64() { return v.Int64() / int64(pow10Uint64[-d.exp]) } scaledD := d.rescale(0) return scaledD.getValue().Int64() } // BigInt returns integer component of the decimal as a BigInt. func (d Decimal) BigInt() *big.Int { scaledD := d.rescale(0) return scaledD.getValue() } // BigFloat returns decimal as BigFloat. // Be aware that casting decimal to BigFloat might cause a loss of precision. func (d Decimal) BigFloat() *big.Float { f := &big.Float{} f.SetString(d.String()) return f } // Rat returns a rational number representation of the decimal. func (d Decimal) Rat() *big.Rat { if d.exp <= 0 { // NOTE(vadim): must negate after casting to prevent int32 overflow return new(big.Rat).SetFrac(d.getValue(), pow10(-int64(d.exp))) } num := new(big.Int).Mul(d.getValue(), pow10(int64(d.exp))) return new(big.Rat).SetFrac(num, oneInt) } // Float64 returns the nearest float64 value for d and a bool indicating // whether f represents d exactly. // For more details, see the documentation for big.Rat.Float64 func (d Decimal) Float64() (f float64, exact bool) { // Rat() materializes 10^|exp|, which takes forever for huge exponents. // Values that far outside the float64 range don't need it: they always // round to zero or overflow to infinity. sign := d.Sign() if sign == 0 { return 0, true } // Both operands are exact here, so the division is correctly rounded like Rat().Float64(), // and the result is exact when the 5^k part of 10^k divides the coefficient. if v := d.getValue(); d.exp <= 0 && d.exp >= -22 && v.IsInt64() { if i := v.Int64(); i >= -1<<53 && i <= 1<<53 { return float64(i) / float64Pow10[-d.exp], i%pow5Int64[-d.exp] == 0 } } // |d| lies in [10^(magnitude-1), 10^magnitude). float64 spans roughly // 1e-324..1e308, so ±400 is safely outside it with margin to spare. magnitude := int64(d.NumDigits()) + int64(d.exp) if magnitude < -400 { return math.Copysign(0, float64(sign)), false } if magnitude > 400 { return math.Inf(sign), false } return d.Rat().Float64() } // InexactFloat64 returns the nearest float64 value for d. // It doesn't indicate if the returned value represents d exactly. func (d Decimal) InexactFloat64() float64 { f, _ := d.Float64() return f } // String returns the string representation of the decimal // with the fixed point. // // Example: // // d := New(-12345, -3) // println(d.String()) // // Output: // // -12.345 func (d Decimal) String() string { return d.string(TrimTrailingZeros, UseScientificNotation) } // StringFixed returns a rounded fixed-point string with places digits after // the decimal point. // // Example: // // NewFromFloat(0).StringFixed(2) // output: "0.00" // NewFromFloat(0).StringFixed(0) // output: "0" // NewFromFloat(5.45).StringFixed(0) // output: "5" // NewFromFloat(5.45).StringFixed(1) // output: "5.5" // NewFromFloat(5.45).StringFixed(2) // output: "5.45" // NewFromFloat(5.45).StringFixed(3) // output: "5.450" // NewFromFloat(545).StringFixed(-1) // output: "540" // // Regardless of the UseScientificNotation option, the returned string will never be in scientific notation. func (d Decimal) StringFixed(places int32) string { rounded := d.Round(places) return rounded.string(false, false) } // StringFixedBank returns a banker rounded fixed-point string with places digits // after the decimal point. // // Example: // // NewFromFloat(0).StringFixedBank(2) // output: "0.00" // NewFromFloat(0).StringFixedBank(0) // output: "0" // NewFromFloat(5.45).StringFixedBank(0) // output: "5" // NewFromFloat(5.45).StringFixedBank(1) // output: "5.4" // NewFromFloat(5.45).StringFixedBank(2) // output: "5.45" // NewFromFloat(5.45).StringFixedBank(3) // output: "5.450" // NewFromFloat(545).StringFixedBank(-1) // output: "540" // // Regardless of the UseScientificNotation option, the returned string will never be in scientific notation. func (d Decimal) StringFixedBank(places int32) string { rounded := d.RoundBank(places) return rounded.string(false, false) } // StringFixedCash returns a Swedish/Cash rounded fixed-point string. For // more details see the documentation at function RoundCash. // // Regardless of the UseScientificNotation option, the returned string will never be in scientific notation. func (d Decimal) StringFixedCash(interval uint8) string { rounded := d.RoundCash(interval) return rounded.string(false, false) } // Round rounds the decimal to places decimal places. // If places < 0, it will round the integer part to the nearest 10^(-places). // // Example: // // NewFromFloat(5.45).Round(1).String() // output: "5.5" // NewFromFloat(545).Round(-1).String() // output: "550" (with UseScientificNotation false, "5.5E2" if true) func (d Decimal) Round(places int32) Decimal { if d.exp == -places { return d } // truncate to places + 1 ret := d.rescale(-places - 1) // add sign(d) * 0.5 if ret.value.Sign() < 0 { ret.value.Sub(ret.value, fiveInt) } else { ret.value.Add(ret.value, fiveInt) } // truncate towards zero ret.value.Quo(ret.value, tenInt) ret.exp++ return ret } // RoundCeil rounds the decimal towards +infinity. // // Example: // // NewFromFloat(545).RoundCeil(-2).String() // output: "600" // NewFromFloat(500).RoundCeil(-2).String() // output: "500" // NewFromFloat(1.1001).RoundCeil(2).String() // output: "1.11" // NewFromFloat(-1.454).RoundCeil(1).String() // output: "-1.4" func (d Decimal) RoundCeil(places int32) Decimal { if d.exp >= -places { return d.rescale(-places) } // q is d truncated to places, r is not 0 when digits were dropped and has the sign of d var r big.Int q := new(big.Int) q.QuoRem(d.getValue(), pow10(-int64(places)-int64(d.exp)), &r) if r.Sign() > 0 { q.Add(q, oneInt) } else if q.Sign() == 0 && r.Sign() != 0 { // reflect.DeepEqual tells an empty word slice from nil, keep the empty one that // rounding a nonzero value to zero used to leave q.SetBits([]big.Word{}) } return Decimal{value: q, exp: -places} } // RoundFloor rounds the decimal towards -infinity. // // Example: // // NewFromFloat(545).RoundFloor(-2).String() // output: "500" // NewFromFloat(-500).RoundFloor(-2).String() // output: "-500" // NewFromFloat(1.1001).RoundFloor(2).String() // output: "1.1" // NewFromFloat(-1.454).RoundFloor(1).String() // output: "-1.5" func (d Decimal) RoundFloor(places int32) Decimal { if d.exp >= -places { return d.rescale(-places) } // q is d truncated to places, r is not 0 when digits were dropped and has the sign of d var r big.Int q := new(big.Int) q.QuoRem(d.getValue(), pow10(-int64(places)-int64(d.exp)), &r) if r.Sign() < 0 { q.Sub(q, oneInt) } else if q.Sign() == 0 && r.Sign() != 0 { // reflect.DeepEqual tells an empty word slice from nil, keep the empty one that // rounding a nonzero value to zero used to leave q.SetBits([]big.Word{}) } return Decimal{value: q, exp: -places} } // RoundUp rounds the decimal away from zero. // // Example: // // NewFromFloat(545).RoundUp(-2).String() // output: "600" // NewFromFloat(500).RoundUp(-2).String() // output: "500" // NewFromFloat(1.1001).RoundUp(2).String() // output: "1.11" // NewFromFloat(-1.454).RoundUp(1).String() // output: "-1.5" func (d Decimal) RoundUp(places int32) Decimal { if d.exp >= -places { return d.rescale(-places) } // q is d truncated to places, r is not 0 when digits were dropped and has the sign of d var r big.Int q := new(big.Int) q.QuoRem(d.getValue(), pow10(-int64(places)-int64(d.exp)), &r) if r.Sign() > 0 { q.Add(q, oneInt) } else if r.Sign() < 0 { q.Sub(q, oneInt) } return Decimal{value: q, exp: -places} } // RoundDown rounds the decimal towards zero. // // Example: // // NewFromFloat(545).RoundDown(-2).String() // output: "500" // NewFromFloat(-500).RoundDown(-2).String() // output: "-500" // NewFromFloat(1.1001).RoundDown(2).String() // output: "1.1" // NewFromFloat(-1.454).RoundDown(1).String() // output: "-1.4" func (d Decimal) RoundDown(places int32) Decimal { return d.rescale(-places) } // RoundBank rounds the decimal to places decimal places. // If the final digit to round is equidistant from the nearest two integers the // rounded value is taken as the even number // // If places < 0, it will round the integer part to the nearest 10^(-places). // // Examples: // // NewFromFloat(5.45).RoundBank(1).String() // output: "5.4" // NewFromFloat(545).RoundBank(-1).String() // output: "540" // NewFromFloat(5.46).RoundBank(1).String() // output: "5.5" // NewFromFloat(546).RoundBank(-1).String() // output: "550" // NewFromFloat(5.55).RoundBank(1).String() // output: "5.6" // NewFromFloat(555).RoundBank(-1).String() // output: "560" func (d Decimal) RoundBank(places int32) Decimal { round := d.Round(places) // it is a tie when twice the k dropped digits equal 10^k if k := -int64(places) - int64(d.exp); k > 0 && round.getValue().Bit(0) != 0 { var dropped big.Int dropped.Rem(d.getValue(), pow10(k)) if dropped.Abs(&dropped).Lsh(&dropped, 1).Cmp(pow10(k)) == 0 { if round.getValue().Sign() < 0 { round.value = new(big.Int).Add(round.getValue(), oneInt) } else { round.value = new(big.Int).Sub(round.getValue(), oneInt) } } } return round } // RoundCash aka Cash/Penny/öre rounding rounds decimal to a specific // interval. The amount payable for a cash transaction is rounded to the nearest // multiple of the minimum currency unit available. The following intervals are // available: 5, 10, 25, 50 and 100; any other number throws a panic. // // 5: 5 cent rounding 3.43 => 3.45 // 10: 10 cent rounding 3.45 => 3.50 (5 gets rounded up) // 25: 25 cent rounding 3.41 => 3.50 // 50: 50 cent rounding 3.75 => 4.00 // 100: 100 cent rounding 3.50 => 4.00 // // For more details: https://en.wikipedia.org/wiki/Cash_rounding func (d Decimal) RoundCash(interval uint8) Decimal { var iVal *big.Int switch interval { case 5: iVal = twentyInt case 10: iVal = tenInt case 25: iVal = fourInt case 50: iVal = twoInt case 100: iVal = oneInt default: panic(fmt.Sprintf("Decimal does not support this Cash rounding interval `%d`. Supported: 5, 10, 25, 50, 100", interval)) } dVal := Decimal{ value: iVal, } return d.Mul(dVal).Round(0).DivRound(dVal, 2) } // Floor returns the nearest integer value less than or equal to d. func (d Decimal) Floor() Decimal { if d.exp >= 0 { return d } // NOTE(vadim): must negate after casting to prevent int32 overflow z := new(big.Int).Div(d.getValue(), pow10(-int64(d.exp))) return Decimal{value: z, exp: 0} } // Ceil returns the nearest integer value greater than or equal to d. func (d Decimal) Ceil() Decimal { if d.exp >= 0 { return d } // NOTE(vadim): must negate after casting to prevent int32 overflow z, m := new(big.Int).DivMod(d.getValue(), pow10(-int64(d.exp)), new(big.Int)) if m.Cmp(zeroInt) != 0 { z.Add(z, oneInt) } return Decimal{value: z, exp: 0} } // Truncate truncates off digits from the number, without rounding. // // If precision >= 0, it specifies the number of decimal places to keep. // If precision < 0, it truncates the integer part to the nearest 10^(-precision) // towards zero. // // Example: // // decimal.NewFromString("123.456").Truncate(2).String() // "123.45" // decimal.NewFromString("5432").Truncate(-2).String() // "5400" // decimal.NewFromString("-5432").Truncate(-2).String() // "-5400" func (d Decimal) Truncate(precision int32) Decimal { if -precision > d.exp { return d.rescale(-precision) } return d } // StringScaled first scales the decimal then calls .String() on it. // // Deprecated: buggy and unintuitive. Use StringFixed instead. func (d Decimal) StringScaled(exp int32) string { return d.rescale(exp).String() } func (d Decimal) string(trimTrailingZeros, useScientificNotation bool) string { if d.exp == 0 { return d.getValue().String() } if d.exp >= 0 { if useScientificNotation { return d.ScientificNotationString() } return d.rescale(0).value.String() } str := d.getValue().String() sign := "" if str[0] == '-' { sign, str = "-", str[1:] } var intPart, leadingZeros, fractionalPart string // NOTE(vadim): this cast to int will cause bugs if d.exp == INT_MIN // and you are on a 32-bit machine. Won't fix this super-edge case. dExpInt := int(d.exp) if len(str) > -dExpInt { intPart = str[:len(str)+dExpInt] fractionalPart = str[len(str)+dExpInt:] } else { intPart = "0" num0s := -dExpInt - len(str) if num0s <= len(zeros) { leadingZeros = zeros[:num0s] } else { leadingZeros = strings.Repeat("0", num0s) } fractionalPart = str } if trimTrailingZeros { i := len(fractionalPart) - 1 for ; i >= 0; i-- { if fractionalPart[i] != '0' { break } } fractionalPart = fractionalPart[:i+1] if fractionalPart == "" { leadingZeros = "" } } if len(leadingZeros)+len(fractionalPart) > 0 { return sign + intPart + "." + leadingZeros + fractionalPart } return sign + intPart } // ScientificNotationString serializes the decimal into standard scientific notation. // // The notation is normalized to have one non-zero digit followed by a decimal point and // the remaining significant digits followed by "E" and the base-10 exponent. // // A zero, which has no significant digits, is simply serialized to "0". func (d Decimal) ScientificNotationString() string { exp := int(d.exp) intStr := new(big.Int).Abs(d.getValue()).String() if intStr == "0" { return intStr } first := intStr[0] var remaining string if len(intStr) > 1 { remaining = "." + intStr[1:] exp = exp + len(intStr) - 1 } number := string(first) + remaining + "E" + strconv.Itoa(exp) if d.value.Sign() < 0 { return "-" + number } return number } // Min returns the smallest Decimal that was passed in the arguments. // // To call this function with an array, you must do: // // Min(arr[0], arr[1:]...) // // This makes it harder to accidentally call Min with 0 arguments. func Min(first Decimal, rest ...Decimal) Decimal { ans := first for _, item := range rest { if item.Cmp(ans) < 0 { ans = item } } return ans } // Max returns the largest Decimal that was passed in the arguments. // // To call this function with an array, you must do: // // Max(arr[0], arr[1:]...) // // This makes it harder to accidentally call Max with 0 arguments. func Max(first Decimal, rest ...Decimal) Decimal { ans := first for _, item := range rest { if item.Cmp(ans) > 0 { ans = item } } return ans } // Sum returns the combined total of the provided first and rest Decimals func Sum(first Decimal, rest ...Decimal) Decimal { if len(rest) == 0 { return first } // Add returns a new value, so the following items can be added to it in place total := first.Add(rest[0]) last := len(rest) - 1 if last == 0 { return total } var scaled big.Int for _, item := range rest[1:last] { switch { case item.exp < total.exp: total = total.rescale(item.exp) total.value.Add(total.value, item.getValue()) case item.exp > total.exp: total.value.Add(total.value, scaled.Mul(item.getValue(), pow10(int64(item.exp)-int64(total.exp)))) default: total.value.Add(total.value, item.getValue()) } } // the last Add builds the result the same way as adding one item at a time does return total.Add(rest[last]) } // Avg returns the average value of the provided first and rest Decimals func Avg(first Decimal, rest ...Decimal) Decimal { count := New(int64(len(rest)+1), 0) sum := Sum(first, rest...) return sum.Div(count) } // RescalePair rescales two decimals to common exponential value (minimal exp of both decimals) func RescalePair(d1 Decimal, d2 Decimal) (Decimal, Decimal) { if d1.exp < d2.exp { return d1, d2.rescale(d1.exp) } else if d1.exp > d2.exp { return d1.rescale(d2.exp), d2 } return d1, d2 }