Skip to content

Commit

Permalink
The calculation precision (16) is sometimes not enough.
Browse files Browse the repository at this point in the history
Fixes #10.

Bug report and tests by Chad Kunde.
  • Loading branch information
bojanz committed May 18, 2021
1 parent e18a432 commit 02a2f48
Show file tree
Hide file tree
Showing 2 changed files with 24 additions and 6 deletions.
24 changes: 18 additions & 6 deletions amount.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ func (a Amount) Convert(currencyCode, rate string) (Amount, error) {
if err != nil {
return Amount{}, InvalidNumberError{"Amount.Convert", rate}
}
ctx := apd.BaseContext.WithPrecision(16)
ctx := decimalContext(a.number, result)
ctx.Mul(result, a.number, result)

return Amount{result, currencyCode}, nil
Expand All @@ -155,7 +155,7 @@ func (a Amount) Add(b Amount) (Amount, error) {
return Amount{}, MismatchError{"Amount.Add", a, b}
}
result := apd.New(0, 0)
ctx := apd.BaseContext.WithPrecision(16)
ctx := decimalContext(a.number, b.number)
ctx.Add(result, a.number, b.number)

return Amount{result, a.currencyCode}, nil
Expand All @@ -167,7 +167,7 @@ func (a Amount) Sub(b Amount) (Amount, error) {
return Amount{}, MismatchError{"Amount.Sub", a, b}
}
result := apd.New(0, 0)
ctx := apd.BaseContext.WithPrecision(16)
ctx := decimalContext(a.number, b.number)
ctx.Sub(result, a.number, b.number)

return Amount{result, a.currencyCode}, nil
Expand All @@ -179,7 +179,7 @@ func (a Amount) Mul(n string) (Amount, error) {
if err != nil {
return Amount{}, InvalidNumberError{"Amount.Mul", n}
}
ctx := apd.BaseContext.WithPrecision(16)
ctx := decimalContext(a.number, result)
ctx.Mul(result, a.number, result)

return Amount{result, a.currencyCode}, err
Expand All @@ -191,7 +191,7 @@ func (a Amount) Div(n string) (Amount, error) {
if err != nil || result.IsZero() {
return Amount{}, InvalidNumberError{"Amount.Div", n}
}
ctx := apd.BaseContext.WithPrecision(16)
ctx := decimalContext(a.number, result)
ctx.Quo(result, a.number, result)

return Amount{result, a.currencyCode}, err
Expand All @@ -214,7 +214,7 @@ func (a Amount) RoundTo(digits uint8, mode RoundingMode) Amount {
RoundDown: apd.RoundDown,
}
result := apd.New(0, 0)
ctx := apd.BaseContext.WithPrecision(16)
ctx := decimalContext(a.number)
ctx.Rounding = extModes[mode]
ctx.Quantize(result, a.number, -int32(digits))

Expand Down Expand Up @@ -355,3 +355,15 @@ func (a *Amount) Scan(src interface{}) error {

return nil
}

// decimalContext returns the decimal context to use for a calculation.
func decimalContext(decimals ...*apd.Decimal) *apd.Context {
// Choose between decimal64 (19 digits) and decimal128 (39 digits)
// based on operand size, for increased performance.
for _, d := range decimals {
if d.Coeff.BitLen() > 31 {
return apd.BaseContext.WithPrecision(39)
}
}
return apd.BaseContext.WithPrecision(19)
}
6 changes: 6 additions & 0 deletions amount_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,12 @@ func TestAmount_RoundTo(t *testing.T) {
{"12.345", 0, currency.RoundHalfDown, "12"},
{"12.345", 0, currency.RoundUp, "13"},
{"12.345", 0, currency.RoundDown, "12"},

// Large amounts (> max int64).
{"12345678901234567890.0345", 3, currency.RoundHalfUp, "12345678901234567890.035"},
{"12345678901234567890.0345", 3, currency.RoundHalfDown, "12345678901234567890.034"},
{"12345678901234567890.0345", 3, currency.RoundUp, "12345678901234567890.035"},
{"12345678901234567890.0345", 3, currency.RoundDown, "12345678901234567890.034"},
}

for _, tt := range tests {
Expand Down

0 comments on commit 02a2f48

Please sign in to comment.