Skip to content

Commit

Permalink
Switch from cockroachdb/apd to ericlagergren/decimal. Fixes #1
Browse files Browse the repository at this point in the history
  • Loading branch information
bojanz committed Jun 15, 2020
1 parent 4b80b7d commit 2a46a5c
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 58 deletions.
81 changes: 37 additions & 44 deletions amount.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"encoding/json"
"fmt"

"github.com/cockroachdb/apd/v2"
"github.com/ericlagergren/decimal"
)

// RoundingMode determines how the amount will be rounded.
Expand Down Expand Up @@ -58,14 +58,14 @@ func (e MismatchError) Error() string {

// Amount stores a decimal number with its currency code.
type Amount struct {
number *apd.Decimal
number *decimal.Big
currencyCode string
}

// NewAmount creates a new Amount from a numeric string and a currency code.
func NewAmount(n, currencyCode string) (Amount, error) {
number, _, err := apd.NewFromString(n)
if err != nil {
number, ok := new(decimal.Big).SetString(n)
if !ok || number.IsNaN(0) {
return Amount{}, InvalidNumberError{"NewAmount", n}
}
if !IsValid(currencyCode) {
Expand Down Expand Up @@ -98,20 +98,21 @@ func (a Amount) ToMinorUnits() int64 {
if a.number == nil {
return 0
}
return a.Round().number.Coeff.Int64()
result, _ := a.Round().number.SetScale(0).Int64()

return result
}

// Convert converts a to a different currency.
func (a Amount) Convert(currencyCode, rate string) (Amount, error) {
if !IsValid(currencyCode) {
return Amount{}, InvalidCurrencyCodeError{"Amount.Convert", currencyCode}
}
result, _, err := apd.NewFromString(rate)
if err != nil {
result, ok := new(decimal.Big).SetString(rate)
if !ok || result.IsNaN(0) {
return Amount{}, InvalidNumberError{"Amount.Convert", rate}
}
ctx := apd.BaseContext.WithPrecision(16)
ctx.Mul(result, a.number, result)
result.Mul(a.number, result)

return Amount{result, currencyCode}, nil
}
Expand All @@ -121,9 +122,8 @@ func (a Amount) Add(b Amount) (Amount, error) {
if a.currencyCode != b.currencyCode {
return Amount{}, MismatchError{"Amount.Add", a, b}
}
result := apd.New(0, 0)
ctx := apd.BaseContext.WithPrecision(16)
ctx.Add(result, a.number, b.number)
result := new(decimal.Big)
result.Add(a.number, b.number)

return Amount{result, a.currencyCode}, nil
}
Expand All @@ -133,35 +133,32 @@ func (a Amount) Sub(b Amount) (Amount, error) {
if a.currencyCode != b.currencyCode {
return Amount{}, MismatchError{"Amount.Sub", a, b}
}
result := apd.New(0, 0)
ctx := apd.BaseContext.WithPrecision(16)
ctx.Sub(result, a.number, b.number)
result := new(decimal.Big)
result.Sub(a.number, b.number)

return Amount{result, a.currencyCode}, nil
}

// Mul multiplies a by n and returns the result.
func (a Amount) Mul(n string) (Amount, error) {
result, _, err := apd.NewFromString(n)
if err != nil {
result, ok := new(decimal.Big).SetString(n)
if !ok || result.IsNaN(0) {
return Amount{}, InvalidNumberError{"Amount.Mul", n}
}
ctx := apd.BaseContext.WithPrecision(16)
ctx.Mul(result, a.number, result)
result.Mul(a.number, result)

return Amount{result, a.currencyCode}, err
return Amount{result, a.currencyCode}, nil
}

// Div divides a by n and returns the result.
func (a Amount) Div(n string) (Amount, error) {
result, _, err := apd.NewFromString(n)
if err != nil || result.IsZero() {
result, ok := new(decimal.Big).SetString(n)
if !ok || result.IsNaN(0) || result.Sign() == 0 {
return Amount{}, InvalidNumberError{"Amount.Div", n}
}
ctx := apd.BaseContext.WithPrecision(16)
ctx.Quo(result, a.number, result)
result.Quo(a.number, result)

return Amount{result, a.currencyCode}, err
return Amount{result, a.currencyCode}, nil
}

// Round is a shortcut for RoundTo(currency.DefaultDigits, currency.RoundHalfUp).
Expand All @@ -174,16 +171,15 @@ func (a Amount) RoundTo(digits uint8, mode RoundingMode) Amount {
if digits == DefaultDigits {
digits, _ = GetDigits(a.currencyCode)
}
extModes := map[RoundingMode]string{
RoundHalfUp: apd.RoundHalfUp,
RoundHalfDown: apd.RoundHalfDown,
RoundUp: apd.RoundUp,
RoundDown: apd.RoundDown,
extModes := map[RoundingMode]decimal.RoundingMode{
RoundHalfUp: decimal.ToNearestAway,
RoundHalfDown: decimal.ToNearestTowardZero,
RoundUp: decimal.AwayFromZero,
RoundDown: decimal.ToZero,
}
result := apd.New(0, 0)
ctx := apd.BaseContext.WithPrecision(16)
ctx.Rounding = extModes[mode]
ctx.Quantize(result, a.number, -int32(digits))
result := new(decimal.Big).Copy(a.number)
result.Context.RoundingMode = extModes[mode]
result.Quantize(int(digits))

return Amount{result, a.currencyCode}
}
Expand Down Expand Up @@ -211,20 +207,17 @@ func (a Amount) Equal(b Amount) bool {

// IsPositive returns whether a is positive.
func (a Amount) IsPositive() bool {
zero := apd.New(0, 0)
return a.number.Cmp(zero) == 1
return a.number.Sign() == 1
}

// IsNegative returns whether a is negative.
func (a Amount) IsNegative() bool {
zero := apd.New(0, 0)
return a.number.Cmp(zero) == -1
return a.number.Sign() == -1
}

// IsZero returns whether a is zero.
func (a Amount) IsZero() bool {
zero := apd.New(0, 0)
return a.number.Cmp(zero) == 0
return a.number.Sign() == 0
}

// MarshalBinary implements the encoding.BinaryMarshaler interface.
Expand All @@ -243,8 +236,8 @@ func (a *Amount) UnmarshalBinary(data []byte) error {
}
n := string(data[3:])
currencyCode := string(data[0:3])
number, _, err := apd.NewFromString(n)
if err != nil {
number, ok := decimal.WithContext(decimal.Context64).SetString(n)
if !ok || number.IsNaN(0) {
return InvalidNumberError{"Amount.UnmarshalBinary", n}
}
if !IsValid(currencyCode) {
Expand Down Expand Up @@ -277,8 +270,8 @@ func (a *Amount) UnmarshalJSON(data []byte) error {
if err != nil {
return err
}
number, _, err := apd.NewFromString(aux.Number)
if err != nil {
number, ok := new(decimal.Big).SetString(aux.Number)
if !ok || number.IsNaN(0) {
return InvalidNumberError{"Amount.UnmarshalJSON", aux.Number}
}
if !IsValid(aux.CurrencyCode) {
Expand Down
6 changes: 3 additions & 3 deletions amount_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -514,15 +514,15 @@ func TestAmount_MarshalJSON(t *testing.T) {
}

func TestAmount_UnmarshalJSON(t *testing.T) {
d := []byte(`{"number":"INVALID","currency":"USD"}`)
d := []byte(`{"number":"3,45","currency":"USD"}`)
unmarshalled := &currency.Amount{}
err := json.Unmarshal(d, unmarshalled)
if e, ok := err.(currency.InvalidNumberError); ok {
if e.Op != "Amount.UnmarshalJSON" {
t.Errorf("got %v, want Amount.UnmarshalJSON", e.Op)
}
if e.Number != "INVALID" {
t.Errorf("got %v, want INVALID", e.Number)
if e.Number != "3,45" {
t.Errorf("got %v, want 3,45", e.Number)
}
} else {
t.Errorf("got %T, want currency.InvalidNumberError", err)
Expand Down
6 changes: 1 addition & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,4 @@ module github.com/bojanz/currency

go 1.14

require (
github.com/cockroachdb/apd/v2 v2.0.2
github.com/lib/pq v1.7.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
)
require github.com/ericlagergren/decimal v0.0.0-20191206042408-88212e6cfca9
13 changes: 7 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b8034E=
github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw=
github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY=
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/apmckinlay/gsuneido v0.0.0-20190404155041-0b6cd442a18f/go.mod h1:JU2DOj5Fc6rol0yaT79Csr47QR0vONGwJtBNGRD7jmc=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/ericlagergren/decimal v0.0.0-20191206042408-88212e6cfca9 h1:mMVotm9OVwoOS2IFGRRS5AfMTFWhtf8wj34JEYh47/k=
github.com/ericlagergren/decimal v0.0.0-20191206042408-88212e6cfca9/go.mod h1:ZWP59etEywfyMG2lAqnoi3t8uoiZCiTmLtwt6iESIsQ=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=

0 comments on commit 2a46a5c

Please sign in to comment.