Compare commits

..

1 commit

Author SHA1 Message Date
Ali J. Alaee
6f843cac49
Merge cd5465e26b into f55dd56454 2022-04-29 12:09:20 +09:00
7 changed files with 147 additions and 481 deletions

View file

@ -1,16 +1,16 @@
name: ci name: decimal-ci
on: [push] on: [push]
jobs: jobs:
ci-job: ci-job:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
go: [ '1.7.x', '1.18', '1.19', '1.20', '1.21', '1.x' ] go: [ '1.7.x', '1.15', '1.16', '1.17', '1.x' ]
name: Go ${{ matrix.go }} name: Go ${{ matrix.go }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v2
- name: Use go - name: Use go
uses: actions/setup-go@v5 uses: actions/setup-go@v2
with: with:
go-version: ${{ matrix.go }} go-version: ${{ matrix.go }}
- run: go build . - run: go build .

View file

@ -1,6 +1,6 @@
# decimal # decimal
[![ci](https://github.com/shopspring/decimal/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/shopspring/decimal/actions/workflows/ci.yml) [![Github Actions](https://github.com/shopspring/decimal/actions/workflows/ci.yml/badge.svg)](https://github.com/shopspring/decimal/actions/workflows/ci.yml)
[![GoDoc](https://godoc.org/github.com/shopspring/decimal?status.svg)](https://godoc.org/github.com/shopspring/decimal) [![GoDoc](https://godoc.org/github.com/shopspring/decimal?status.svg)](https://godoc.org/github.com/shopspring/decimal)
[![Go Report Card](https://goreportcard.com/badge/github.com/shopspring/decimal)](https://goreportcard.com/report/github.com/shopspring/decimal) [![Go Report Card](https://goreportcard.com/badge/github.com/shopspring/decimal)](https://goreportcard.com/report/github.com/shopspring/decimal)

View file

@ -1,63 +0,0 @@
package decimal
import (
"strings"
)
const (
strLn10 = "2.302585092994045684017991454684364207601101488628772976033327900967572609677352480235997205089598298341967784042286248633409525465082806756666287369098781689482907208325554680843799894826233198528393505308965377732628846163366222287698219886746543667474404243274365155048934314939391479619404400222105101714174800368808401264708068556774321622835522011480466371565912137345074785694768346361679210180644507064800027750268491674655058685693567342067058113642922455440575892572420824131469568901675894025677631135691929203337658714166023010570308963457207544037084746994016826928280848118428931484852494864487192780967627127577539702766860595249671667418348570442250719796500471495105049221477656763693866297697952211071826454973477266242570942932258279850258550978526538320760672631716430950599508780752371033310119785754733154142180842754386359177811705430982748238504564801909561029929182431823752535770975053956518769751037497088869218020518933950723853920514463419726528728696511086257149219884997874887377134568620916705849807828059751193854445009978131146915934666241071846692310107598438319191292230792503747298650929009880391941702654416816335727555703151596113564846546190897042819763365836983716328982174407366009162177850541779276367731145041782137660111010731042397832521894898817597921798666394319523936855916447118246753245630912528778330963604262982153040874560927760726641354787576616262926568298704957954913954918049209069438580790032763017941503117866862092408537949861264933479354871737451675809537088281067452440105892444976479686075120275724181874989395971643105518848195288330746699317814634930000321200327765654130472621883970596794457943468343218395304414844803701305753674262153675579814770458031413637793236291560128185336498466942261465206459942072917119370602444929358037007718981097362533224548366988505528285966192805098447175198503666680874970496982273220244823343097169111136813588418696549323714996941979687803008850408979618598756579894836445212043698216415292987811742973332588607915912510967187510929248475023930572665446276200923068791518135803477701295593646298412366497023355174586195564772461857717369368404676577047874319780573853271810933883496338813069945569399346101090745616033312247949360455361849123333063704751724871276379140924398331810164737823379692265637682071706935846394531616949411701841938119405416449466111274712819705817783293841742231409930022911502362192186723337268385688273533371925103412930705632544426611429765388301822384091026198582888433587455960453004548370789052578473166283701953392231047527564998119228742789713715713228319641003422124210082180679525276689858180956119208391760721080919923461516952599099473782780648128058792731993893453415320185969711021407542282796298237068941764740642225757212455392526179373652434440560595336591539160312524480149313234572453879524389036839236450507881731359711238145323701508413491122324390927681724749607955799151363982881058285740538000653371655553014196332241918087621018204919492651483892"
)
var (
ln10 = newConstApproximation(strLn10)
)
type constApproximation struct {
exact Decimal
approximations []Decimal
}
func newConstApproximation(value string) constApproximation {
parts := strings.Split(value, ".")
coeff, fractional := parts[0], parts[1]
coeffLen := len(coeff)
maxPrecision := len(fractional)
var approximations []Decimal
for p := 1; p < maxPrecision; p *= 2 {
r := RequireFromString(value[:coeffLen+p])
approximations = append(approximations, r)
}
return constApproximation{
RequireFromString(value),
approximations,
}
}
// Returns the smallest approximation available that's at least as precise
// as the passed precision (places after decimal point), i.e. Floor[ log2(precision) ] + 1
func (c constApproximation) withPrecision(precision int32) Decimal {
i := 0
if precision >= 1 {
i++
}
for precision >= 16 {
precision /= 16
i += 4
}
for precision >= 2 {
precision /= 2
i++
}
if i >= len(c.approximations) {
return c.exact
}
return c.approximations[i]
}

View file

@ -1,34 +0,0 @@
package decimal
import "testing"
func TestConstApproximation(t *testing.T) {
for _, testCase := range []struct {
Const string
Precision int32
ExpectedApproximation string
}{
{"2.3025850929940456840179914546", 0, "2"},
{"2.3025850929940456840179914546", 1, "2.3"},
{"2.3025850929940456840179914546", 3, "2.302"},
{"2.3025850929940456840179914546", 5, "2.302585"},
{"2.3025850929940456840179914546", 10, "2.302585092994045"},
{"2.3025850929940456840179914546", 100, "2.3025850929940456840179914546"},
{"2.3025850929940456840179914546", -1, "2"},
{"2.3025850929940456840179914546", -5, "2"},
{"3.14159265359", 0, "3"},
{"3.14159265359", 1, "3.1"},
{"3.14159265359", 2, "3.141"},
{"3.14159265359", 4, "3.1415926"},
{"3.14159265359", 13, "3.14159265359"},
} {
ca := newConstApproximation(testCase.Const)
expected, _ := NewFromString(testCase.ExpectedApproximation)
approximation := ca.withPrecision(testCase.Precision)
if approximation.Cmp(expected) != 0 {
t.Errorf("expected approximation %s, got %s - for const with %s precision %d", testCase.ExpectedApproximation, approximation.String(), testCase.Const, testCase.Precision)
}
}
}

View file

@ -9,7 +9,7 @@
// //
// To use Decimal as part of a struct: // To use Decimal as part of a struct:
// //
// type StructName struct { // type Struct struct {
// Number Decimal // Number Decimal
// } // }
// //
@ -41,6 +41,7 @@ import (
// decimal.DivisionPrecision = 3 // decimal.DivisionPrecision = 3
// d4 := decimal.NewFromFloat(2).Div(decimal.NewFromFloat(3)) // d4 := decimal.NewFromFloat(2).Div(decimal.NewFromFloat(3))
// d4.String() // output: "0.667" // d4.String() // output: "0.667"
//
var DivisionPrecision = 16 var DivisionPrecision = 16
// MarshalJSONWithoutQuotes should be set to true if you want the decimal to // MarshalJSONWithoutQuotes should be set to true if you want the decimal to
@ -90,7 +91,7 @@ func New(value int64, exp int32) Decimal {
} }
} }
// NewFromInt converts an int64 to Decimal. // NewFromInt converts a int64 to Decimal.
// //
// Example: // Example:
// //
@ -103,7 +104,7 @@ func NewFromInt(value int64) Decimal {
} }
} }
// NewFromInt32 converts an int32 to Decimal. // NewFromInt32 converts a int32 to Decimal.
// //
// Example: // Example:
// //
@ -124,26 +125,6 @@ func NewFromBigInt(value *big.Int, exp int32) Decimal {
} }
} }
// 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. // NewFromString returns a new Decimal from a string representation.
// Trailing zeroes are not trimmed. // Trailing zeroes are not trimmed.
// //
@ -152,6 +133,7 @@ func NewFromBigRat(value *big.Rat, precision int32) Decimal {
// d, err := NewFromString("-123.45") // d, err := NewFromString("-123.45")
// d2, err := NewFromString(".0001") // d2, err := NewFromString(".0001")
// d3, err := NewFromString("1.47000") // d3, err := NewFromString("1.47000")
//
func NewFromString(value string) (Decimal, error) { func NewFromString(value string) (Decimal, error) {
originalInput := value originalInput := value
var intString string var intString string
@ -237,6 +219,7 @@ func NewFromString(value string) (Decimal, error) {
// //
// r3 := regexp.MustCompile("[USD\\s]") // r3 := regexp.MustCompile("[USD\\s]")
// d3, err := NewFromFormattedString("5000 USD", r3) // d3, err := NewFromFormattedString("5000 USD", r3)
//
func NewFromFormattedString(value string, replRegexp *regexp.Regexp) (Decimal, error) { func NewFromFormattedString(value string, replRegexp *regexp.Regexp) (Decimal, error) {
parsedValue := replRegexp.ReplaceAllString(value, "") parsedValue := replRegexp.ReplaceAllString(value, "")
d, err := NewFromString(parsedValue) d, err := NewFromString(parsedValue)
@ -247,12 +230,13 @@ func NewFromFormattedString(value string, replRegexp *regexp.Regexp) (Decimal, e
} }
// RequireFromString returns a new Decimal from a string representation // RequireFromString returns a new Decimal from a string representation
// or panics if NewFromString had returned an error. // or panics if NewFromString would have returned an error.
// //
// Example: // Example:
// //
// d := RequireFromString("-123.45") // d := RequireFromString("-123.45")
// d2 := RequireFromString(".0001") // d2 := RequireFromString(".0001")
//
func RequireFromString(value string) Decimal { func RequireFromString(value string) Decimal {
dec, err := NewFromString(value) dec, err := NewFromString(value)
if err != nil { if err != nil {
@ -349,6 +333,7 @@ func newFromFloat(val float64, bits uint64, flt *floatInfo) Decimal {
// Example: // Example:
// //
// NewFromFloatWithExponent(123.456, -2).String() // output: "123.46" // NewFromFloatWithExponent(123.456, -2).String() // output: "123.46"
//
func NewFromFloatWithExponent(value float64, exp int32) Decimal { func NewFromFloatWithExponent(value float64, exp int32) Decimal {
if math.IsNaN(value) || math.IsInf(value, 0) { if math.IsNaN(value) || math.IsInf(value, 0) {
panic(fmt.Sprintf("Cannot create a Decimal from %v", value)) panic(fmt.Sprintf("Cannot create a Decimal from %v", value))
@ -433,7 +418,7 @@ func NewFromFloatWithExponent(value float64, exp int32) Decimal {
func (d Decimal) Copy() Decimal { func (d Decimal) Copy() Decimal {
d.ensureInitialized() d.ensureInitialized()
return Decimal{ return Decimal{
value: new(big.Int).Set(d.value), value: &(*d.value),
exp: d.exp, exp: d.exp,
} }
} }
@ -457,6 +442,7 @@ func (d Decimal) Copy() Decimal {
// 1.2345 // 1.2345
// 1.2 // 1.2
// 1.2000 // 1.2000
//
func (d Decimal) rescale(exp int32) Decimal { func (d Decimal) rescale(exp int32) Decimal {
d.ensureInitialized() d.ensureInitialized()
@ -568,11 +554,9 @@ func (d Decimal) Div(d2 Decimal) Decimal {
// QuoRem does division with remainder // QuoRem does division with remainder
// d.QuoRem(d2,precision) returns quotient q and remainder r such that // d.QuoRem(d2,precision) returns quotient q and remainder r such that
//
// d = d2 * q + r, q an integer multiple of 10^(-precision) // 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
// 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. // Note that precision<0 is allowed as input.
func (d Decimal) QuoRem(d2 Decimal, precision int32) (Decimal, Decimal) { func (d Decimal) QuoRem(d2 Decimal, precision int32) (Decimal, Decimal) {
d.ensureInitialized() d.ensureInitialized()
@ -581,7 +565,7 @@ func (d Decimal) QuoRem(d2 Decimal, precision int32) (Decimal, Decimal) {
panic("decimal division by 0") panic("decimal division by 0")
} }
scale := -precision scale := -precision
e := int64(d.exp) - int64(d2.exp) - int64(scale) e := int64(d.exp - d2.exp - scale)
if e > math.MaxInt32 || e < math.MinInt32 { if e > math.MaxInt32 || e < math.MinInt32 {
panic("overflow in decimal QuoRem") panic("overflow in decimal QuoRem")
} }
@ -615,10 +599,8 @@ func (d Decimal) QuoRem(d2 Decimal, precision int32) (Decimal, Decimal) {
// DivRound divides and rounds to a given precision // DivRound divides and rounds to a given precision
// i.e. to an integer multiple of 10^(-precision) // i.e. to an integer multiple of 10^(-precision)
//
// for a positive quotient digit 5 is rounded up, away from 0 // 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 // if the quotient is negative then digit 5 is rounded down, away from 0
//
// Note that precision<0 is allowed as input. // Note that precision<0 is allowed as input.
func (d Decimal) DivRound(d2 Decimal, precision int32) Decimal { func (d Decimal) DivRound(d2 Decimal, precision int32) Decimal {
// QuoRem already checks initialization // QuoRem already checks initialization
@ -646,8 +628,8 @@ func (d Decimal) DivRound(d2 Decimal, precision int32) Decimal {
// Mod returns d % d2. // Mod returns d % d2.
func (d Decimal) Mod(d2 Decimal) Decimal { func (d Decimal) Mod(d2 Decimal) Decimal {
_, r := d.QuoRem(d2, 0) quo := d.DivRound(d2, -d.exp+1).Truncate(0)
return r return d.Sub(d2.Mul(quo))
} }
// Pow returns d to the power d2 // Pow returns d to the power d2
@ -675,6 +657,7 @@ func (d Decimal) Pow(d2 Decimal) Decimal {
// //
// NewFromFloat(26.1).ExpHullAbrham(2).String() // output: "220000000000" // NewFromFloat(26.1).ExpHullAbrham(2).String() // output: "220000000000"
// NewFromFloat(26.1).ExpHullAbrham(20).String() // output: "216314672147.05767284" // NewFromFloat(26.1).ExpHullAbrham(20).String() // output: "216314672147.05767284"
//
func (d Decimal) ExpHullAbrham(overallPrecision uint32) (Decimal, error) { func (d Decimal) ExpHullAbrham(overallPrecision uint32) (Decimal, error) {
// Algorithm based on Variable precision exponential function. // Algorithm based on Variable precision exponential function.
// ACM Transactions on Mathematical Software by T. E. Hull & A. Abrham. // ACM Transactions on Mathematical Software by T. E. Hull & A. Abrham.
@ -772,6 +755,7 @@ func (d Decimal) ExpHullAbrham(overallPrecision uint32) (Decimal, error) {
// //
// NewFromFloat(26.1).ExpTaylor(-10).String() // NewFromFloat(26.1).ExpTaylor(-10).String()
// d.String() // output: "220000000000" // d.String() // output: "220000000000"
//
func (d Decimal) ExpTaylor(precision int32) (Decimal, error) { func (d Decimal) ExpTaylor(precision int32) (Decimal, error) {
// Note(mwoss): Implementation can be optimized by exclusively using big.Int API only // Note(mwoss): Implementation can be optimized by exclusively using big.Int API only
if d.IsZero() { if d.IsZero() {
@ -828,127 +812,9 @@ func (d Decimal) ExpTaylor(precision int32) (Decimal, error) {
return result, nil return result, nil
} }
// Ln calculates natural logarithm of d.
// Precision argument specifies how precise the result must be (number of digits after decimal point).
// Negative precision is allowed.
//
// Example:
//
// d1, err := NewFromFloat(13.3).Ln(2)
// d1.String() // output: "2.59"
//
// d2, err := NewFromFloat(579.161).Ln(10)
// d2.String() // output: "6.3615805046"
func (d Decimal) Ln(precision int32) (Decimal, error) {
// Algorithm based on The Use of Iteration Methods for Approximating the Natural Logarithm,
// James F. Epperson, The American Mathematical Monthly, Vol. 96, No. 9, November 1989, pp. 831-835.
if d.IsNegative() {
return Decimal{}, fmt.Errorf("cannot calculate natural logarithm for negative decimals")
}
if d.IsZero() {
return Decimal{}, fmt.Errorf("cannot represent natural logarithm of 0, result: -infinity")
}
calcPrecision := precision + 2
z := d.Copy()
var comp1, comp3, comp2, comp4, reduceAdjust Decimal
comp1 = z.Sub(Decimal{oneInt, 0})
comp3 = Decimal{oneInt, -1}
// for decimal in range [0.9, 1.1] where ln(d) is close to 0
usePowerSeries := false
if comp1.Abs().Cmp(comp3) <= 0 {
usePowerSeries = true
} else {
// reduce input decimal to range [0.1, 1)
expDelta := int32(z.NumDigits()) + z.exp
z.exp -= expDelta
// Input decimal was reduced by factor of 10^expDelta, thus we will need to add
// ln(10^expDelta) = expDelta * ln(10)
// to the result to compensate that
ln10 := ln10.withPrecision(calcPrecision)
reduceAdjust = NewFromInt32(expDelta)
reduceAdjust = reduceAdjust.Mul(ln10)
comp1 = z.Sub(Decimal{oneInt, 0})
if comp1.Abs().Cmp(comp3) <= 0 {
usePowerSeries = true
} else {
// initial estimate using floats
zFloat := z.InexactFloat64()
comp1 = NewFromFloat(math.Log(zFloat))
}
}
epsilon := Decimal{oneInt, -calcPrecision}
if usePowerSeries {
// Power Series - https://en.wikipedia.org/wiki/Logarithm#Power_series
// Calculating n-th term of formula: ln(z+1) = 2 sum [ 1 / (2n+1) * (z / (z+2))^(2n+1) ]
// until the difference between current and next term is smaller than epsilon.
// Coverage quite fast for decimals close to 1.0
// z + 2
comp2 = comp1.Add(Decimal{twoInt, 0})
// z / (z + 2)
comp3 = comp1.DivRound(comp2, calcPrecision)
// 2 * (z / (z + 2))
comp1 = comp3.Add(comp3)
comp2 = comp1.Copy()
for n := 1; ; n++ {
// 2 * (z / (z+2))^(2n+1)
comp2 = comp2.Mul(comp3).Mul(comp3)
// 1 / (2n+1) * 2 * (z / (z+2))^(2n+1)
comp4 = NewFromInt(int64(2*n + 1))
comp4 = comp2.DivRound(comp4, calcPrecision)
// comp1 = 2 sum [ 1 / (2n+1) * (z / (z+2))^(2n+1) ]
comp1 = comp1.Add(comp4)
if comp4.Abs().Cmp(epsilon) <= 0 {
break
}
}
} else {
// Halley's Iteration.
// Calculating n-th term of formula: a_(n+1) = a_n - 2 * (exp(a_n) - z) / (exp(a_n) + z),
// until the difference between current and next term is smaller than epsilon
for {
// exp(a_n)
comp3, _ = comp1.ExpTaylor(calcPrecision)
// exp(a_n) - z
comp2 = comp3.Sub(z)
// 2 * (exp(a_n) - z)
comp2 = comp2.Add(comp2)
// exp(a_n) + z
comp4 = comp3.Add(z)
// 2 * (exp(a_n) - z) / (exp(a_n) + z)
comp3 = comp2.DivRound(comp4, calcPrecision)
// comp1 = a_(n+1) = a_n - 2 * (exp(a_n) - z) / (exp(a_n) + z)
comp1 = comp1.Sub(comp3)
if comp3.Abs().Cmp(epsilon) <= 0 {
break
}
}
}
comp1 = comp1.Add(reduceAdjust)
return comp1.Round(precision), nil
}
// NumDigits returns the number of digits of the decimal coefficient (d.Value) // NumDigits returns the number of digits of the decimal coefficient (d.Value)
// Note: Current implementation is extremely slow for large decimals and/or decimals with large fractional part // Note: Current implementation is extremely slow for large decimals and/or decimals with large fractional part
func (d Decimal) NumDigits() int { func (d Decimal) NumDigits() int {
d.ensureInitialized()
// Note(mwoss): It can be optimized, unnecessary cast of big.Int to string // Note(mwoss): It can be optimized, unnecessary cast of big.Int to string
if d.IsNegative() { if d.IsNegative() {
return len(d.value.String()) - 1 return len(d.value.String()) - 1
@ -988,6 +854,7 @@ func abs(n int32) int32 {
// -1 if d < d2 // -1 if d < d2
// 0 if d == d2 // 0 if d == d2
// +1 if d > d2 // +1 if d > d2
//
func (d Decimal) Cmp(d2 Decimal) int { func (d Decimal) Cmp(d2 Decimal) int {
d.ensureInitialized() d.ensureInitialized()
d2.ensureInitialized() d2.ensureInitialized()
@ -1001,21 +868,12 @@ func (d Decimal) Cmp(d2 Decimal) int {
return rd.value.Cmp(rd2.value) return rd.value.Cmp(rd2.value)
} }
// 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. // Equal returns whether the numbers represented by d and d2 are equal.
func (d Decimal) Equal(d2 Decimal) bool { func (d Decimal) Equal(d2 Decimal) bool {
return d.Cmp(d2) == 0 return d.Cmp(d2) == 0
} }
// Deprecated: Equals is deprecated, please use Equal method instead. // Equals is deprecated, please use Equal method instead
func (d Decimal) Equals(d2 Decimal) bool { func (d Decimal) Equals(d2 Decimal) bool {
return d.Equal(d2) return d.Equal(d2)
} }
@ -1047,6 +905,7 @@ func (d Decimal) LessThanOrEqual(d2 Decimal) bool {
// -1 if d < 0 // -1 if d < 0
// 0 if d == 0 // 0 if d == 0
// +1 if d > 0 // +1 if d > 0
//
func (d Decimal) Sign() int { func (d Decimal) Sign() int {
if d.value == nil { if d.value == nil {
return 0 return 0
@ -1161,6 +1020,7 @@ func (d Decimal) InexactFloat64() float64 {
// Output: // Output:
// //
// -12.345 // -12.345
//
func (d Decimal) String() string { func (d Decimal) String() string {
return d.string(true) return d.string(true)
} }
@ -1177,6 +1037,7 @@ func (d Decimal) String() string {
// NewFromFloat(5.45).StringFixed(2) // output: "5.45" // NewFromFloat(5.45).StringFixed(2) // output: "5.45"
// NewFromFloat(5.45).StringFixed(3) // output: "5.450" // NewFromFloat(5.45).StringFixed(3) // output: "5.450"
// NewFromFloat(545).StringFixed(-1) // output: "550" // NewFromFloat(545).StringFixed(-1) // output: "550"
//
func (d Decimal) StringFixed(places int32) string { func (d Decimal) StringFixed(places int32) string {
rounded := d.Round(places) rounded := d.Round(places)
return rounded.string(false) return rounded.string(false)
@ -1194,6 +1055,7 @@ func (d Decimal) StringFixed(places int32) string {
// NewFromFloat(5.45).StringFixedBank(2) // output: "5.45" // NewFromFloat(5.45).StringFixedBank(2) // output: "5.45"
// NewFromFloat(5.45).StringFixedBank(3) // output: "5.450" // NewFromFloat(5.45).StringFixedBank(3) // output: "5.450"
// NewFromFloat(545).StringFixedBank(-1) // output: "540" // NewFromFloat(545).StringFixedBank(-1) // output: "540"
//
func (d Decimal) StringFixedBank(places int32) string { func (d Decimal) StringFixedBank(places int32) string {
rounded := d.RoundBank(places) rounded := d.RoundBank(places)
return rounded.string(false) return rounded.string(false)
@ -1213,6 +1075,7 @@ func (d Decimal) StringFixedCash(interval uint8) string {
// //
// NewFromFloat(5.45).Round(1).String() // output: "5.5" // NewFromFloat(5.45).Round(1).String() // output: "5.5"
// NewFromFloat(545).Round(-1).String() // output: "550" // NewFromFloat(545).Round(-1).String() // output: "550"
//
func (d Decimal) Round(places int32) Decimal { func (d Decimal) Round(places int32) Decimal {
if d.exp == -places { if d.exp == -places {
return d return d
@ -1244,7 +1107,8 @@ func (d Decimal) Round(places int32) Decimal {
// NewFromFloat(545).RoundCeil(-2).String() // output: "600" // NewFromFloat(545).RoundCeil(-2).String() // output: "600"
// NewFromFloat(500).RoundCeil(-2).String() // output: "500" // NewFromFloat(500).RoundCeil(-2).String() // output: "500"
// NewFromFloat(1.1001).RoundCeil(2).String() // output: "1.11" // NewFromFloat(1.1001).RoundCeil(2).String() // output: "1.11"
// NewFromFloat(-1.454).RoundCeil(1).String() // output: "-1.4" // NewFromFloat(-1.454).RoundCeil(1).String() // output: "-1.5"
//
func (d Decimal) RoundCeil(places int32) Decimal { func (d Decimal) RoundCeil(places int32) Decimal {
if d.exp >= -places { if d.exp >= -places {
return d return d
@ -1269,7 +1133,8 @@ func (d Decimal) RoundCeil(places int32) Decimal {
// NewFromFloat(545).RoundFloor(-2).String() // output: "500" // NewFromFloat(545).RoundFloor(-2).String() // output: "500"
// NewFromFloat(-500).RoundFloor(-2).String() // output: "-500" // NewFromFloat(-500).RoundFloor(-2).String() // output: "-500"
// NewFromFloat(1.1001).RoundFloor(2).String() // output: "1.1" // NewFromFloat(1.1001).RoundFloor(2).String() // output: "1.1"
// NewFromFloat(-1.454).RoundFloor(1).String() // output: "-1.5" // NewFromFloat(-1.454).RoundFloor(1).String() // output: "-1.4"
//
func (d Decimal) RoundFloor(places int32) Decimal { func (d Decimal) RoundFloor(places int32) Decimal {
if d.exp >= -places { if d.exp >= -places {
return d return d
@ -1294,7 +1159,8 @@ func (d Decimal) RoundFloor(places int32) Decimal {
// NewFromFloat(545).RoundUp(-2).String() // output: "600" // NewFromFloat(545).RoundUp(-2).String() // output: "600"
// NewFromFloat(500).RoundUp(-2).String() // output: "500" // NewFromFloat(500).RoundUp(-2).String() // output: "500"
// NewFromFloat(1.1001).RoundUp(2).String() // output: "1.11" // NewFromFloat(1.1001).RoundUp(2).String() // output: "1.11"
// NewFromFloat(-1.454).RoundUp(1).String() // output: "-1.5" // NewFromFloat(-1.454).RoundUp(1).String() // output: "-1.4"
//
func (d Decimal) RoundUp(places int32) Decimal { func (d Decimal) RoundUp(places int32) Decimal {
if d.exp >= -places { if d.exp >= -places {
return d return d
@ -1321,7 +1187,8 @@ func (d Decimal) RoundUp(places int32) Decimal {
// NewFromFloat(545).RoundDown(-2).String() // output: "500" // NewFromFloat(545).RoundDown(-2).String() // output: "500"
// NewFromFloat(-500).RoundDown(-2).String() // output: "-500" // NewFromFloat(-500).RoundDown(-2).String() // output: "-500"
// NewFromFloat(1.1001).RoundDown(2).String() // output: "1.1" // NewFromFloat(1.1001).RoundDown(2).String() // output: "1.1"
// NewFromFloat(-1.454).RoundDown(1).String() // output: "-1.4" // NewFromFloat(-1.454).RoundDown(1).String() // output: "-1.5"
//
func (d Decimal) RoundDown(places int32) Decimal { func (d Decimal) RoundDown(places int32) Decimal {
if d.exp >= -places { if d.exp >= -places {
return d return d
@ -1348,6 +1215,7 @@ func (d Decimal) RoundDown(places int32) Decimal {
// NewFromFloat(546).RoundBank(-1).String() // output: "550" // NewFromFloat(546).RoundBank(-1).String() // output: "550"
// NewFromFloat(5.55).RoundBank(1).String() // output: "5.6" // NewFromFloat(5.55).RoundBank(1).String() // output: "5.6"
// NewFromFloat(555).RoundBank(-1).String() // output: "560" // NewFromFloat(555).RoundBank(-1).String() // output: "560"
//
func (d Decimal) RoundBank(places int32) Decimal { func (d Decimal) RoundBank(places int32) Decimal {
round := d.Round(places) round := d.Round(places)
@ -1369,13 +1237,11 @@ func (d Decimal) RoundBank(places int32) Decimal {
// interval. The amount payable for a cash transaction is rounded to the nearest // interval. The amount payable for a cash transaction is rounded to the nearest
// multiple of the minimum currency unit available. The following intervals are // multiple of the minimum currency unit available. The following intervals are
// available: 5, 10, 25, 50 and 100; any other number throws a panic. // available: 5, 10, 25, 50 and 100; any other number throws a panic.
//
// 5: 5 cent rounding 3.43 => 3.45 // 5: 5 cent rounding 3.43 => 3.45
// 10: 10 cent rounding 3.45 => 3.50 (5 gets rounded up) // 10: 10 cent rounding 3.45 => 3.50 (5 gets rounded up)
// 25: 25 cent rounding 3.41 => 3.50 // 25: 25 cent rounding 3.41 => 3.50
// 50: 50 cent rounding 3.75 => 4.00 // 50: 50 cent rounding 3.75 => 4.00
// 100: 100 cent rounding 3.50 => 4.00 // 100: 100 cent rounding 3.50 => 4.00
//
// For more details: https://en.wikipedia.org/wiki/Cash_rounding // For more details: https://en.wikipedia.org/wiki/Cash_rounding
func (d Decimal) RoundCash(interval uint8) Decimal { func (d Decimal) RoundCash(interval uint8) Decimal {
var iVal *big.Int var iVal *big.Int
@ -1445,6 +1311,7 @@ func (d Decimal) Ceil() Decimal {
// Example: // Example:
// //
// decimal.NewFromString("123.456").Truncate(2).String() // "123.45" // decimal.NewFromString("123.456").Truncate(2).String() // "123.45"
//
func (d Decimal) Truncate(precision int32) Decimal { func (d Decimal) Truncate(precision int32) Decimal {
d.ensureInitialized() d.ensureInitialized()
if precision >= 0 && -precision > d.exp { if precision >= 0 && -precision > d.exp {
@ -1588,8 +1455,7 @@ func (d *Decimal) GobDecode(data []byte) error {
} }
// StringScaled first scales the decimal then calls .String() on it. // StringScaled first scales the decimal then calls .String() on it.
// // NOTE: buggy, unintuitive, and DEPRECATED! Use StringFixed instead.
// Deprecated: buggy and unintuitive. Use StringFixed instead.
func (d Decimal) StringScaled(exp int32) string { func (d Decimal) StringScaled(exp int32) string {
return d.rescale(exp).String() return d.rescale(exp).String()
} }
@ -1701,13 +1567,22 @@ func RescalePair(d1 Decimal, d2 Decimal) (Decimal, Decimal) {
d1.ensureInitialized() d1.ensureInitialized()
d2.ensureInitialized() d2.ensureInitialized()
if d1.exp < d2.exp { if d1.exp == d2.exp {
return d1, d2.rescale(d1.exp) return d1, d2
} else if d1.exp > d2.exp {
return d1.rescale(d2.exp), d2
} }
return d1, d2 baseScale := min(d1.exp, d2.exp)
if baseScale != d1.exp {
return d1.rescale(baseScale), d2
}
return d1, d2.rescale(baseScale)
}
func min(x, y int32) int32 {
if x >= y {
return y
}
return x
} }
func unquoteIfQuoted(value interface{}) (string, error) { func unquoteIfQuoted(value interface{}) (string, error) {
@ -1719,7 +1594,8 @@ func unquoteIfQuoted(value interface{}) (string, error) {
case []byte: case []byte:
bytes = v bytes = v
default: default:
return "", fmt.Errorf("could not convert value '%+v' to byte array of type '%T'", value, value) return "", fmt.Errorf("could not convert value '%+v' to byte array of type '%T'",
value, value)
} }
// If the amount is quoted, strip the quotes // If the amount is quoted, strip the quotes

View file

@ -623,59 +623,10 @@ func TestNewFromBigIntWithExponent(t *testing.T) {
} }
} }
func TestNewFromBigRat(t *testing.T) {
mustParseRat := func(val string) *big.Rat {
num, _ := new(big.Rat).SetString(val)
return num
}
type Inp struct {
val *big.Rat
prec int32
}
tests := map[Inp]string{
Inp{big.NewRat(0, 1), 16}: "0",
Inp{big.NewRat(4, 5), 16}: "0.8",
Inp{big.NewRat(10, 2), 16}: "5",
Inp{big.NewRat(1023427554493, 43432632), 16}: "23563.5628642767953828", // rounded
Inp{big.NewRat(1, 434324545566634), 16}: "0.0000000000000023",
Inp{big.NewRat(1, 3), 16}: "0.3333333333333333",
Inp{big.NewRat(2, 3), 2}: "0.67", // rounded
Inp{big.NewRat(2, 3), 16}: "0.6666666666666667", // rounded
Inp{big.NewRat(10000, 3), 16}: "3333.3333333333333333",
Inp{mustParseRat("30702832066636633479"), 16}: "30702832066636633479",
Inp{mustParseRat("487028320159896636679.1827512895753"), 16}: "487028320159896636679.1827512895753",
Inp{mustParseRat("127028320612589896636633479.173582751289575278357832"), -2}: "127028320612589896636633500", // rounded
Inp{mustParseRat("127028320612589896636633479.173582751289575278357832"), 16}: "127028320612589896636633479.1735827512895753", // rounded
Inp{mustParseRat("127028320612589896636633479.173582751289575278357832"), 32}: "127028320612589896636633479.173582751289575278357832",
}
// add negatives
for p, s := range tests {
if p.val.Cmp(new(big.Rat)) > 0 {
tests[Inp{p.val.Neg(p.val), p.prec}] = "-" + s
}
}
for input, s := range tests {
d := NewFromBigRat(input.val, input.prec)
if d.String() != s {
t.Errorf("expected %s, got %s (%s, %d)",
s, d.String(),
d.value.String(), d.exp)
}
}
}
func TestCopy(t *testing.T) { func TestCopy(t *testing.T) {
origin := New(1, 0) origin := New(1, 0)
cpy := origin.Copy() cpy := origin.Copy()
if origin.value == cpy.value {
t.Error("expecting copy and origin to have different value pointers")
}
if cpy.Cmp(origin) != 0 { if cpy.Cmp(origin) != 0 {
t.Error("expecting copy and origin to be equals, but they are not") t.Error("expecting copy and origin to be equals, but they are not")
} }
@ -2259,8 +2210,6 @@ func TestDecimal_Mod(t *testing.T) {
Inp{"-7.5", "2"}: "-1.5", Inp{"-7.5", "2"}: "-1.5",
Inp{"7.5", "-2"}: "1.5", Inp{"7.5", "-2"}: "1.5",
Inp{"-7.5", "-2"}: "-1.5", Inp{"-7.5", "-2"}: "-1.5",
Inp{"41", "21"}: "20",
Inp{"400000000001", "200000000001"}: "200000000000",
} }
for inp, res := range inputs { for inp, res := range inputs {
@ -2863,67 +2812,6 @@ func TestDecimal_ExpTaylor(t *testing.T) {
} }
} }
func TestDecimal_Ln(t *testing.T) {
for _, testCase := range []struct {
Dec string
Precision int32
Expected string
}{
{"0.1", 25, "-2.3025850929940456840179915"},
{"0.01", 25, "-4.6051701859880913680359829"},
{"0.001", 25, "-6.9077552789821370520539744"},
{"0.00000001", 25, "-18.4206807439523654721439316"},
{"1.0", 10, "0.0"},
{"1.01", 25, "0.0099503308531680828482154"},
{"1.001", 25, "0.0009995003330835331668094"},
{"1.0001", 25, "0.0000999950003333083353332"},
{"1.1", 25, "0.0953101798043248600439521"},
{"1.13", 25, "0.1222176327242492005461486"},
{"3.13", 10, "1.1410330046"},
{"3.13", 25, "1.1410330045520618486427824"},
{"3.13", 50, "1.14103300455206184864278239988848193837089629107972"},
{"3.13", 100, "1.1410330045520618486427823998884819383708962910797239760817078430268177216960996098918971117211892839"},
{"5.71", 25, "1.7422190236679188486939833"},
{"5.7185108151957193571930205", 50, "1.74370842450178929149992165925283704012576949094645"},
{"839101.0351", 25, "13.6400864014410013994397240"},
{"839101.0351094726488848490572028502", 50, "13.64008640145229044389152437468283605382056561604272"},
{"5023583755703750094849.03519358513093500275017501750602739169823", 25, "49.9684305274348922267409953"},
{"5023583755703750094849.03519358513093500275017501750602739169823", -1, "50.0"},
} {
d, _ := NewFromString(testCase.Dec)
expected, _ := NewFromString(testCase.Expected)
ln, err := d.Ln(testCase.Precision)
if err != nil {
t.Fatal(err)
}
if ln.Cmp(expected) != 0 {
t.Errorf("expected %s, got %s, for decimal %s", testCase.Expected, ln.String(), testCase.Dec)
}
}
}
func TestDecimal_LnZero(t *testing.T) {
d := New(0, 0)
_, err := d.Ln(5)
if err == nil {
t.Errorf("expected error, natural logarithm of 0 cannot be represented (-infinity)")
}
}
func TestDecimal_LnNegative(t *testing.T) {
d := New(-20, 2)
_, err := d.Ln(5)
if err == nil {
t.Errorf("expected error, natural logarithm cannot be calculated for nagative decimals")
}
}
func TestDecimal_NumDigits(t *testing.T) { func TestDecimal_NumDigits(t *testing.T) {
for _, testCase := range []struct { for _, testCase := range []struct {
Dec string Dec string
@ -2943,7 +2831,6 @@ func TestDecimal_NumDigits(t *testing.T) {
{"-5.26", 3}, {"-5.26", 3},
{"-5.2663117716", 11}, {"-5.2663117716", 11},
{"-26.1", 3}, {"-26.1", 3},
{"", 1},
} { } {
d, _ := NewFromString(testCase.Dec) d, _ := NewFromString(testCase.Dec)

2
go.mod
View file

@ -1,3 +1,3 @@
module github.com/shopspring/decimal module github.com/shopspring/decimal
go 1.7 go 1.13