From b592b84bf097be0460e6f88a27e547ca58cf879f Mon Sep 17 00:00:00 2001 From: Nikolay Dubina Date: Sat, 6 Apr 2024 15:27:58 +0800 Subject: [PATCH 1/3] gen --- README.md | 236 ++------------ amount.go | 142 ++------- amount_test.go | 413 ++++-------------------- currency.go | 229 ++++++++++--- currency_enum.go | 565 --------------------------------- currency_enum_encoding.go | 386 ++++++++++++++++++++++ currency_enum_encoding_test.go | 74 +++++ currency_test.go | 80 ----- error.go | 15 - 9 files changed, 767 insertions(+), 1373 deletions(-) delete mode 100644 currency_enum.go create mode 100644 currency_enum_encoding.go create mode 100644 currency_enum_encoding_test.go delete mode 100644 currency_test.go delete mode 100644 error.go diff --git a/README.md b/README.md index e17679a..ac2b32e 100644 --- a/README.md +++ b/README.md @@ -18,11 +18,10 @@ * block mismatched currency arithmetics * does not leak precision * parsing faster than `int`, `float`, `string` -* Fuzz tests, Benchmarks, Generics -* 200 LOC +* 100 LOC ```go -var BuySP500Price = fpmoney.FromInt(9000, fpmoney.SGD) +var BuySP500Price = fpmoney.Amount{Amount: fpdecimal.FromInt(9000), Currency: fpmoney.SGD} input := []byte(`{"sp500": {"amount": 9000.02, "currency": "SGD"}}`) @@ -34,13 +33,13 @@ if err := json.Unmarshal(input, &v); err != nil { log.Fatal(err) } -amountToBuy := fpmoney.FromInt(0, fpmoney.SGD) +amountToBuy := fpmoney.Amount{Amount: fpdecimal.Zero, Currency: fpmoney.SGD} if v.SP500.GreaterThan(BuySP500Price) { amountToBuy = amountToBuy.Add(v.SP500.Mul(2)) } -fmt.Println(amountToBuy) -// Output: 18000.04 SGD +json.NewEncoder(os.Stdout).Encode(amountToBuy) +// Output: {"amount":18000.04,"currency":"SGD"} ``` ### Division @@ -49,49 +48,19 @@ Division always returns remainder. Fractional cents can never be reached. ```go -x := fpmoney.FromInt(1, fpmoney.SGD) +x := fpmoney.Amount{Amount: fpdecimal.FromInt(1), Currency: fpmoney.SGD} a, r := x.Div(3) -fmt.Println(a, r) -// Output: 0.33 SGD 0.01 SGD - -a, r = x.Div(5) -fmt.Println(a, r) -// Output: 0.2 SGD 0 SGD -``` - -### Equality - -Equality operator can be used to compare values. - -```go -x := fpmoney.FromInt(3, fpmoney.SGD) -y := fpmoney.FromInt(9, fpmoney.SGD) -fmt.Println(y == x.Mul(3)) -// Output: true -``` - -### Cross Currency Protection - -Akin to integer division by 0 which panics in Go, mismatched currenices result in panic. - -Arithmetics -```go -x := fpmoney.FromInt(10, fpmoney.USD) -y := fpmoney.FromInt(10, fpmoney.SGD) -c := x.Add(y) // panics -``` - -Equality -```go -x := fpmoney.FromInt(10, fpmoney.USD) -y := fpmoney.FromInt(10, fpmoney.SGD) -fmt.Println(y == x) -// Output: false +enc := json.NewEncoder(os.Stdout) +enc.Encode(a) +enc.Encode(r) +// Output: +// {"amount":0.3333,"currency":"SGD"} +// {"amount":0.0001,"currency":"SGD"} ``` ### Ultra Small Fractions -Some denominatinos have very low fractions. +Some denominations have very low fractions. Storing them `int64` you would get. - `BTC` _satoshi_ is `1 BTC = 100,000,000 satoshi`, which is still enough for ~`92,233,720,368 BTC`. @@ -103,172 +72,33 @@ Implementing this is area of furthter research. ### Benchmarks -``` -$ go test -bench=. -benchmem ./... -goos: darwin -goarch: arm64 -pkg: github.com/nikolaydubina/fpmoney -BenchmarkArithmetic/add_x1-10 1000000000 0.5 ns/op 0 B/op 0 allocs/op -BenchmarkArithmetic/add_x100-10 12525424 51.9 ns/op 0 B/op 0 allocs/op -BenchmarkJSONUnmarshal/small-10 3610992 329.8 ns/op 198 B/op 3 allocs/op -BenchmarkJSONUnmarshal/large-10 2901363 412.4 ns/op 216 B/op 3 allocs/op -BenchmarkJSONMarshal/small-10 5032456 238.1 ns/op 160 B/op 3 allocs/op -BenchmarkJSONMarshal/large-10 4072776 295.5 ns/op 176 B/op 3 allocs/op -BenchmarkJSONMarshal_Exact/small-10 40404832 29.6 ns/op 112 B/op 1 allocs/op -BenchmarkJSONMarshal_Exact/large-10 28532677 41.6 ns/op 112 B/op 1 allocs/op -PASS -ok github.com/nikolaydubina/fpmoney 62.744s -``` - -`float32` (old) and `fpmoney` (new) -``` -$ benchstat -split="XYZ" float32.bench fpmoney.bench -name old time/op new time/op delta -JSONUnmarshal/small-10 502ns ± 0% 338ns ± 1% -32.63% (p=0.008 n=5+5) -JSONUnmarshal/large-10 572ns ± 0% 419ns ± 1% -26.79% (p=0.008 n=5+5) -JSONMarshal/small-10 189ns ± 0% 245ns ± 1% +29.12% (p=0.008 n=5+5) -JSONMarshal/large-10 176ns ± 0% 305ns ± 1% +73.07% (p=0.008 n=5+5) - -name old alloc/op new alloc/op delta -JSONUnmarshal/small-10 271B ± 0% 198B ± 0% -26.94% (p=0.008 n=5+5) -JSONUnmarshal/large-10 312B ± 0% 216B ± 0% -30.77% (p=0.008 n=5+5) -JSONMarshal/small-10 66.0B ± 0% 160.0B ± 0% +142.42% (p=0.008 n=5+5) -JSONMarshal/large-10 72.0B ± 0% 176.0B ± 0% +144.44% (p=0.008 n=5+5) - -name old allocs/op new allocs/op delta -JSONUnmarshal/small-10 6.00 ± 0% 3.00 ± 0% -50.00% (p=0.008 n=5+5) -JSONUnmarshal/large-10 7.00 ± 0% 3.00 ± 0% -57.14% (p=0.008 n=5+5) -JSONMarshal/small-10 2.00 ± 0% 3.00 ± 0% +50.00% (p=0.008 n=5+5) -JSONMarshal/large-10 2.00 ± 0% 3.00 ± 0% +50.00% (p=0.008 n=5+5) -``` - -`int`, `float32`, `fpmoney` -``` +```bash +$ go test -bench=. -benchmem . > fpmoney.bench +$ go test -bench=. -benchmem ./internal/bench/float32 > float32.bench +$ go test -bench=. -benchmem ./internal/bench/int > int.bench $ benchstat -split="XYZ" int.bench float32.bench fpmoney.bench name \ time/op int.bench float32.bench fpmoney.bench -JSONUnmarshal/small-10 481ns ± 2% 502ns ± 0% 338ns ± 1% -JSONUnmarshal/large-10 530ns ± 1% 572ns ± 0% 419ns ± 1% -JSONMarshal/small-10 140ns ± 1% 189ns ± 0% 245ns ± 1% -JSONMarshal/large-10 145ns ± 0% 176ns ± 0% 305ns ± 1% +JSONUnmarshal/small-16 382ns ± 0% 429ns ± 0% 419ns ± 0% +JSONUnmarshal/large-16 429ns ± 0% 503ns ± 0% 464ns ± 0% +JSONMarshal/small-16 112ns ± 0% 158ns ± 0% 187ns ± 0% +JSONMarshal/large-16 112ns ± 0% 144ns ± 0% 231ns ± 0% name \ alloc/op int.bench float32.bench fpmoney.bench -JSONUnmarshal/small-10 269B ± 0% 271B ± 0% 198B ± 0% -JSONUnmarshal/large-10 288B ± 0% 312B ± 0% 216B ± 0% -JSONMarshal/small-10 57.0B ± 0% 66.0B ± 0% 160.0B ± 0% -JSONMarshal/large-10 72.0B ± 0% 72.0B ± 0% 176.0B ± 0% +JSONUnmarshal/small-16 268B ± 0% 270B ± 0% 262B ± 0% +JSONUnmarshal/large-16 272B ± 0% 288B ± 0% 280B ± 0% +JSONMarshal/small-16 57.0B ± 0% 66.0B ± 0% 72.0B ± 0% +JSONMarshal/large-16 72.0B ± 0% 72.0B ± 0% 88.0B ± 0% name \ allocs/op int.bench float32.bench fpmoney.bench -JSONUnmarshal/small-10 6.00 ± 0% 6.00 ± 0% 3.00 ± 0% -JSONUnmarshal/large-10 7.00 ± 0% 7.00 ± 0% 3.00 ± 0% -JSONMarshal/small-10 2.00 ± 0% 2.00 ± 0% 3.00 ± 0% -JSONMarshal/large-10 2.00 ± 0% 2.00 ± 0% 3.00 ± 0% +JSONUnmarshal/small-16 6.00 ± 0% 6.00 ± 0% 5.00 ± 0% +JSONUnmarshal/large-16 6.00 ± 0% 6.00 ± 0% 5.00 ± 0% +JSONMarshal/small-16 2.00 ± 0% 2.00 ± 0% 3.00 ± 0% +JSONMarshal/large-16 2.00 ± 0% 2.00 ± 0% 3.00 ± 0% ``` -## References +## References and Related Work - [ferdypruis/iso4217](https://github.com/ferdypruis/iso4217) was a good inspiration and reference material. it was used in early version as well. it is well maintained and fast library for currencies. - -## Appendix A: `json.Unmarshal` optimizations - -Parsing is surprisingly slow. It is ~6x of `float32` + `string`. - -Use `json.NewDecoder` and parse directly. -``` -BenchmarkJSONUnmarshal/small-10 2030568 2977 ns/op 1599 B/op 38 allocs/op -BenchmarkJSONUnmarshal/large-10 1956444 3106 ns/op 1640 B/op 39 allocs/op - -``` - -Make container struct and wrap int and ISO 4217 currency and copy values. -``` -BenchmarkJSONUnmarshal/small-10 2776969 2160 ns/op 430 B/op 8 allocs/op -BenchmarkJSONUnmarshal/large-10 2649692 2263 ns/op 448 B/op 8 allocs/op -``` - -Two passes over string, find `amount` and find `currency`. -``` -BenchmarkJSONUnmarshal/small-10 686832 1732 ns/op 198 B/op 3 allocs/op -BenchmarkJSONUnmarshal/large-10 657272 1820 ns/op 216 B/op 3 allocs/op -``` - -Parsing just amount takes 400ns. -``` -BenchmarkJSONUnmarshal/small-10 3339529 344.5 ns/op 198 B/op 3 allocs/op -BenchmarkJSONUnmarshal/large-10 2686135 443.2 ns/op 216 B/op 3 allocs/op -``` - -Package `github.com/ferdypruis/iso4217@v1.2.0` does cast of string to currency through loop. -But we have predefined currencies, we can rely on compiler for that. -Optimizing this cast by avoiding mallocs and loops. - -As of `2022-06-17`, package `github.com/ferdypruis/iso4217@v1.2.1` uses map to cast currency. -It is as efficient as switch case. -Thanks @ferdypruis for the update! - -## Appendix B: Other Libraries - -`github.com/shopspring/decimal` -* fixed precision -* faster printing/parsing/arithmetics -* currency handling - -`github.com/Rhymond/go-money` -* does not use `float` or `interface{}` in parsing -* currency is enum - -`github.com/ferdypruis/iso4217` -* skipped deprecated currencies to fit into `uint8` and smaller struct size - -## Appendix C: Extra malloc in Printing - -Even though `MarshalJSON` does exactly one malloc, using it with `json.Marshall` package adds two more mallocs. -This looks like penalty of reflect nature of `json` package and is unavoidable. - -``` -BenchmarkJSONMarshal_Exact/small-10 40404832 29.6 ns/op 112 B/op 1 allocs/op -BenchmarkJSONMarshal_Exact/large-10 28532677 41.6 ns/op 112 B/op 1 allocs/op -``` - -## Appendix D: Strict Currency Enum - -It is possible to rely on Go compiler to strictiy currency enum by wrapping into a struct. -There is no performance penalty. -Implementation is almost same. -API is the same, but much safer. - -## Appendix E: comparable generics for currency - -Using `comparable` generic constraint is attractive option, since it allows to plug-in any type for currencies, including from other packages. -Marshalling and Unamrshalling will be fully delegated to other packages too. -However, this inccurs penalty for performance. -Arithmetics is 6x slower, and due to `json` pacakge `mallocs` increase 2x. -Using fpdecimal.Decimal directly, also fixes precision for whole binary, meaning other pacakges in binary should be using same number of decimal values in fpdecimal package. -For large scale systems, it is recommended to not access fpdecimal.Decimal directly, but define wrappers on top of it. -Lastly, fixing number of decimals for all currencies will lead to most curencies (2 decimals) to have 1 fractional digit, which leads to fractional decimals, which is what we try to avoid. - -```go -type Amount[T comparable] struct { - Amount fpdecimal.Decimal `json:"amount"` - Currency T `json:"currency"` -} -``` - -## Appendix F: Global Singleton for currency - -Typically systems have single currency they deal with. -The major feature that is needed for such cases is protection from airhtmetics with non-money types, parsing, printing, and detecting on input bad currency. -This all can be accomplished with global single ton currency string and number of minor units. -Basically, instead of dragging currency enum (uint8) in every single amount variable, we are now reducing amount to contain only `int64` and currency is not global for whole system. -Turns out, there is no much benefit in doing so. All operations, Airthmetics, Printing, Parsing is little bit faster, but almost not noticeable. -Thus, it is fairly safe to include currency enum at runtime for amount at almost no cost. - -``` -BenchmarkArithmetic/add_x1-10 1000000000 0.46 ns/op 0 B/op 0 allocs/op -BenchmarkArithmetic/add_x100-10 27101086 40.69 ns/op 0 B/op 0 allocs/op -BenchmarkJSONUnmarshal/small-10 3822724 308.5 ns/op 152 B/op 2 allocs/op -BenchmarkJSONUnmarshal/large-10 3073228 387.2 ns/op 152 B/op 2 allocs/op -BenchmarkJSONMarshal/small-10 4851312 224.5 ns/op 144 B/op 2 allocs/op -BenchmarkJSONMarshal/large-10 4196076 289.1 ns/op 168 B/op 3 allocs/op -BenchmarkJSONMarshal_Exact/small-10 36865509 29.14 ns/op 112 B/op 1 allocs/op -BenchmarkJSONMarshal_Exact/large-10 29832595 38.86 ns/op 112 B/op 1 allocs/op -``` +- `github.com/shopspring/decimal`: fixed precision; faster printing/parsing/arithmetics; currency handling +- `github.com/Rhymond/go-money`: does not use `float` or `interface{}` in parsing; currency is enum +- `github.com/ferdypruis/iso4217`: skipped deprecated currencies to fit into `uint8` and smaller struct size diff --git a/amount.go b/amount.go index 6e971be..6704cb3 100644 --- a/amount.go +++ b/amount.go @@ -4,65 +4,48 @@ import ( "github.com/nikolaydubina/fpdecimal" ) +func init() { + fpdecimal.FractionDigits = 4 +} + // Amount stores fixed-precision decimal money. -// Stores integer number of cents for ISO 4217 currency. // Values fit in ~92 quadrillion for 2 decimal currencies. // Does not use float in printing nor parsing. // Rounds down fractional cents during parsing. // Blocking arithmetic operations that result in loss of precision. type Amount struct { - v int64 - c Currency - _ uint8 // padding for improved line alignment, 2x improves arithmetics - _ uint32 // padding -} - -func FromIntScaled[T ~int | ~int8 | ~int16 | ~int32 | ~int64](v T, currency Currency) Amount { - return Amount{v: int64(v), c: currency} -} - -func FromInt[T ~int | ~int8 | ~int16 | ~int32 | ~int64](v T, currency Currency) Amount { - return Amount{v: int64(v) * currency.scale(), c: currency} + Amount fpdecimal.Decimal `json:"amount"` + Currency Currency `json:"currency"` } -func FromFloat[T ~float32 | ~float64](v T, currency Currency) Amount { - return Amount{v: int64(T(v) * T(currency.scale())), c: currency} -} - -func (a Amount) Float32() float32 { return float32(a.v) / float32(a.c.scale()) } - -func (a Amount) Float64() float64 { return float64(a.v) / float64(a.c.scale()) } - -func (a Amount) Currency() Currency { return a.c } - func (a Amount) Add(b Amount) Amount { - checkCurrency(a.c, b.c) - return Amount{v: a.v + b.v, c: a.c} + checkCurrency(a.Currency, b.Currency) + return Amount{Amount: a.Amount.Add(b.Amount), Currency: a.Currency} } func (a Amount) Sub(b Amount) Amount { - checkCurrency(a.c, b.c) - return Amount{v: a.v - b.v, c: a.c} + checkCurrency(a.Currency, b.Currency) + return Amount{Amount: a.Amount.Sub(b.Amount), Currency: a.Currency} } func (a Amount) GreaterThan(b Amount) bool { - checkCurrency(a.c, b.c) - return a.v > b.v + checkCurrency(a.Currency, b.Currency) + return a.Amount.GreaterThan(b.Amount) } func (a Amount) LessThan(b Amount) bool { - checkCurrency(a.c, b.c) - return a.v < b.v + checkCurrency(a.Currency, b.Currency) + return a.Amount.LessThan(b.Amount) } func (a Amount) GreaterThanOrEqual(b Amount) bool { - checkCurrency(a.c, b.c) - return a.v >= b.v + checkCurrency(a.Currency, b.Currency) + return a.Amount.GreaterThanOrEqual(b.Amount) } func (a Amount) LessThanOrEqual(b Amount) bool { - checkCurrency(a.c, b.c) - return a.v <= b.v + checkCurrency(a.Currency, b.Currency) + return a.Amount.LessThanOrEqual(b.Amount) } func checkCurrency(a, b Currency) { @@ -71,89 +54,22 @@ func checkCurrency(a, b Currency) { } } -func (a Amount) Mul(b int) Amount { return Amount{v: a.v * int64(b), c: a.c} } +func (a Amount) Mul(b int) Amount { + return Amount{Amount: a.Amount.Mul(fpdecimal.FromInt(b)), Currency: a.Currency} +} func (a Amount) Div(b int) (part Amount, remainder Amount) { - return Amount{v: a.v / int64(b), c: a.c}, Amount{v: a.v % int64(b), c: a.c} + return Amount{Amount: a.Amount.Div(fpdecimal.FromInt(b)), Currency: a.Currency}, Amount{Amount: a.Amount.Mod(fpdecimal.FromInt(b)), Currency: a.Currency} } -func (a Amount) String() string { - return fpdecimal.FixedPointDecimalToString(a.v, a.c.Exponent()) + " " + a.c.Alpha() +type ErrCurrencyMismatch struct { + A, B Currency } -const ( - keyCurrency = "currency" - keyAmount = "amount" - lenISO427Currency = 3 -) - -// UnmarshalJSON parses string. -// This is implemented directly for speed. -// Avoiding json.Decoder, interface{}, reflect, tags, temporary structs. -// Avoiding mallocs. -// Go json package provides: -// - check that pointer method receiver is not nil; -// - removes whitespace in b []bytes -func (a *Amount) UnmarshalJSON(b []byte) (err error) { - var as, ae, e int - - for i := 0; i < len(b); i++ { - // currency - if b[i] == keyCurrency[0] && (i+len(keyCurrency)) <= len(b) && string(b[i:i+len(keyCurrency)]) == keyCurrency { - i += len(keyCurrency) + 2 // `":` - - // find opening quote. - for ; i < len(b) && b[i] != '"'; i++ { - } - if i == len(b) { - return &ErrWrongCurrencyString{} - } - i++ // opening `"` - e = i + lenISO427Currency - if e > len(b) { - return &ErrWrongCurrencyString{} - } - - a.c, err = CurrencyFromAlpha(string(b[i:e])) - if err != nil { - return err - } - i = e - } - - // amount - if b[i] == keyAmount[0] && (i+len(keyCurrency)) <= len(b) && string(b[i:i+len(keyAmount)]) == keyAmount { - i += len(keyAmount) + 2 // `":` - // go until find either number or + or -, which is a start of simple number. - for ; i < len(b) && !(b[i] >= '0' && b[i] <= '9') && b[i] != '-' && b[i] != '+'; i++ { - } - as = i - // find end of number - for ae = i; ae < len(b) && ((b[ae] >= '0' && b[ae] <= '9') || b[ae] == '-' || b[ae] == '+' || b[ae] == '.'); ae++ { - } - i = ae - } - } - - if a.c.IsUndefined() { - return &ErrWrongCurrencyString{} - } - - a.v, err = fpdecimal.ParseFixedPointDecimal(b[as:ae], a.c.Exponent()) - - return err -} +func NewErrCurrencyMismatch() *ErrCurrencyMismatch { return &ErrCurrencyMismatch{} } -func (a Amount) MarshalJSON() ([]byte, error) { - b := make([]byte, 0, 100) - b = append(b, `{"`...) - b = append(b, keyAmount...) - b = append(b, `":`...) - b = fpdecimal.AppendFixedPointDecimal(b, a.v, a.c.Exponent()) - b = append(b, `,"`...) - b = append(b, keyCurrency...) - b = append(b, `":"`...) - b = append(b, a.c.Alpha()...) - b = append(b, `"}`...) - return b, nil +func (e *ErrCurrencyMismatch) Error() string { + a, _ := e.A.MarshalText() + b, _ := e.B.MarshalText() + return string(a) + " != " + string(b) } diff --git a/amount_test.go b/amount_test.go index 8ffa286..db32f0d 100644 --- a/amount_test.go +++ b/amount_test.go @@ -6,15 +6,17 @@ import ( "errors" "fmt" "log" + "os" "strconv" "strings" "testing" + "github.com/nikolaydubina/fpdecimal" "github.com/nikolaydubina/fpmoney" ) func ExampleAmount() { - var BuySP500Price = fpmoney.FromInt(9000, fpmoney.SGD) + var BuySP500Price = fpmoney.Amount{Amount: fpdecimal.FromInt(9000), Currency: fpmoney.SGD} input := []byte(`{"sp500": {"amount": 9000.02, "currency": "SGD"}}`) @@ -26,54 +28,62 @@ func ExampleAmount() { log.Fatal(err) } - amountToBuy := fpmoney.FromInt(0, fpmoney.SGD) + amountToBuy := fpmoney.Amount{Amount: fpdecimal.Zero, Currency: fpmoney.SGD} if v.SP500.GreaterThan(BuySP500Price) { amountToBuy = amountToBuy.Add(v.SP500.Mul(2)) } - fmt.Println(amountToBuy) - // Output: 18000.04 SGD + json.NewEncoder(os.Stdout).Encode(amountToBuy) + // Output: {"amount":18000.04,"currency":"SGD"} } func ExampleAmount_Div_part() { - x := fpmoney.FromInt(1, fpmoney.SGD) + x := fpmoney.Amount{Amount: fpdecimal.FromInt(1), Currency: fpmoney.SGD} a, r := x.Div(3) - fmt.Println(a, r) - // Output: 0.33 SGD 0.01 SGD + enc := json.NewEncoder(os.Stdout) + enc.Encode(a) + enc.Encode(r) + // Output: + // {"amount":0.3333,"currency":"SGD"} + // {"amount":0.0001,"currency":"SGD"} } func ExampleAmount_Div_whole() { - x := fpmoney.FromInt(1, fpmoney.SGD) + x := fpmoney.Amount{Amount: fpdecimal.FromInt(1), Currency: fpmoney.SGD} a, r := x.Div(5) - fmt.Println(a, r) - // Output: 0.2 SGD 0 SGD + enc := json.NewEncoder(os.Stdout) + enc.Encode(a) + enc.Encode(r) + // Output: + // {"amount":0.2,"currency":"SGD"} + // {"amount":0,"currency":"SGD"} } func ExampleAmount_equality() { - x := fpmoney.FromInt(3, fpmoney.SGD) - y := fpmoney.FromInt(9, fpmoney.SGD) + x := fpmoney.Amount{Amount: fpdecimal.FromInt(3), Currency: fpmoney.SGD} + y := fpmoney.Amount{Amount: fpdecimal.FromInt(9), Currency: fpmoney.SGD} fmt.Println(y == x.Mul(3)) // Output: true } func ExampleAmount_equality_same_currency() { - x := fpmoney.FromInt(10, fpmoney.SGD) - y := fpmoney.FromInt(10, fpmoney.SGD) + x := fpmoney.Amount{Amount: fpdecimal.FromInt(10), Currency: fpmoney.SGD} + y := fpmoney.Amount{Amount: fpdecimal.FromInt(10), Currency: fpmoney.SGD} fmt.Println(y == x) // Output: true } func ExampleAmount_equality_wrong_currency() { - x := fpmoney.FromInt(10, fpmoney.USD) - y := fpmoney.FromInt(10, fpmoney.SGD) + x := fpmoney.Amount{Amount: fpdecimal.FromInt(10), Currency: fpmoney.USD} + y := fpmoney.Amount{Amount: fpdecimal.FromInt(10), Currency: fpmoney.SGD} fmt.Println(y == x) // Output: false } func ExampleFromFloat() { - x := fpmoney.FromFloat(144.96, fpmoney.SGD) - fmt.Println(x) - // Output: 144.96 SGD + x := fpmoney.Amount{Amount: fpdecimal.FromFloat(144.96), Currency: fpmoney.SGD} + json.NewEncoder(os.Stdout).Encode(x) + // Output: {"amount":144.96,"currency":"SGD"} } func FuzzArithmetics(f *testing.F) { @@ -101,26 +111,28 @@ func FuzzArithmetics(f *testing.F) { } currency := currencies[c] - fa := fpmoney.FromIntScaled(a, currency) - fb := fpmoney.FromIntScaled(b, currency) + fa := fpmoney.Amount{Amount: fpdecimal.FromIntScaled(a), Currency: currency} + fb := fpmoney.Amount{Amount: fpdecimal.FromIntScaled(b), Currency: currency} + + zero := fpmoney.Amount{Amount: fpdecimal.Zero, Currency: currency} v := []bool{ // sum commutativity fa.Add(fb) == fb.Add(fa), // sum associativity - fpmoney.FromInt(0, currency).Add(fa).Add(fb).Add(fa) == fpmoney.FromInt(0, currency).Add(fb).Add(fa).Add(fa), + zero.Add(fa).Add(fb).Add(fa) == zero.Add(fb).Add(fa).Add(fa), // sum zero fa == fa.Add(fb).Sub(fb), fa == fa.Sub(fb).Add(fb), - fpmoney.FromInt(0, currency) == fpmoney.FromInt(0, currency).Add(fa).Sub(fa), + zero == zero.Add(fa).Sub(fa), // product identity fa == fa.Mul(1), // product zero - fpmoney.FromInt(0, currency) == fa.Mul(0), + zero == fa.Mul(0), // match number (a == b) == (fa == fb), @@ -130,8 +142,8 @@ func FuzzArithmetics(f *testing.F) { a >= b == fa.GreaterThanOrEqual(fb), // match number convert - fpmoney.FromIntScaled(a+b, currency) == fa.Add(fb), - fpmoney.FromIntScaled(a-b, currency) == fa.Sub(fb), + fpmoney.Amount{Amount: fpdecimal.FromIntScaled(a + b), Currency: currency} == fa.Add(fb), + fpmoney.Amount{Amount: fpdecimal.FromIntScaled(a - b), Currency: currency} == fa.Sub(fb), } for i, q := range v { if !q { @@ -141,323 +153,16 @@ func FuzzArithmetics(f *testing.F) { if b != 0 { w, r := fa.Div(int(b)) - if w != fpmoney.FromIntScaled(a/b, currency) { + if w != (fpmoney.Amount{Amount: fpdecimal.FromIntScaled(a / b), Currency: currency}) { t.Error(w, a/b, a, b, fa) } - if r != fpmoney.FromIntScaled(a%b, currency) { + if r != (fpmoney.Amount{Amount: fpdecimal.FromIntScaled(a % b), Currency: currency}) { t.Error(r, a%b, a, b, fa) } } }) } -func FuzzJSONUnmarshal_Float(f *testing.F) { - currencies := [...]fpmoney.Currency{ - fpmoney.KRW, - fpmoney.SGD, - fpmoney.BHD, - fpmoney.CLF, - } - - tests := []float32{ - 0, - 0.100, - 0.101, - 0.010, - 0.001, - 0.0001, - 0.123, - 0.103, - 0.100001, - 12.001, - 12.010, - 12.345, - 1, - 2, - 10, - 12345678, - } - for _, tc := range tests { - for i := range currencies { - f.Add(tc, i, uint8(5)) - f.Add(-tc, i, uint8(5)) - } - } - f.Fuzz(func(t *testing.T, r float32, c int, nf uint8) { - if c > len(currencies)-1 || c < 0 { - t.Skip() - } - if nf > 10 { - t.Skip() - } - var l float32 = 10000000 - if r > l || r < -l { - t.Skip() - } - if c == 0 { - t.Skip() - } - - currency := currencies[c] - - fs := `%.` + strconv.Itoa(int(nf)) + `f` - rs := fmt.Sprintf(fs, r) - s := fmt.Sprintf(`{"amount": %s, "currency": "%s"}`, rs, currency.Alpha()) - if _, err := fmt.Sscanf(rs, "%f", &r); err != nil { - t.Error(err) - } - - if r == -0 { - t.Skip() - } - - var x fpmoney.Amount - if err := json.Unmarshal([]byte(s), &x); err != nil { - t.Error(rs, currency, err) - } - - if x.Currency() != currency { - t.Error(x, currency) - } - }) -} - -func FuzzJSONUnmarshal_NoPanic(f *testing.F) { - amounts := []string{ - "123.456", - "0.123", - "0.1", - "0.01", - "0.001", - "0.000", - "0.123.2", - "0..1", - "0.1.2", - "123.1o2", - "--123", - "00000.123", - "-", - "", - "123456", - } - currencies := []string{ - "SGD", - "SGDSGD", - "", - "123", - "'SGD'", - `"TDF"`, - } - for _, a := range amounts { - for _, c := range currencies { - f.Add(a) - f.Add("-" + a) - f.Add(fmt.Sprintf(`{"amount": %s, "currency": %s}`, a, c)) - f.Add(fmt.Sprintf(`{"amount": -%s, "currency": %s}`, a, c)) - f.Add(fmt.Sprintf(`{"amount": -%s, "currency": %s}`, a, c)) - f.Add(fmt.Sprintf(`"amount": -%s, "currency": %s`, a, c)) - f.Add(fmt.Sprintf(`{"amount": -%s}`, a)) - f.Add(fmt.Sprintf(`{"currency": %s}`, c)) - f.Add(fmt.Sprintf(`{"amount": %s, "currency": %s}`, c, a)) - f.Add(fmt.Sprintf(`"amount": %s, "currency": %s}`, a, c)) - f.Add(fmt.Sprintf(`{"amount": %s, "currency": %s`, c, a)) - f.Add(fmt.Sprintf(`{"amount": %s,,""""currency": %s}`, a, c)) - } - } - - f.Add(`{"amount": 123.32, "currency":""}`) - f.Add(`{"amount": , "currency":""}`) - f.Add(`{"amount":,"currency":""}`) - - f.Fuzz(func(t *testing.T, s string) { - var x fpmoney.Amount - err := json.Unmarshal([]byte(s), &x) - if err != nil { - if (x != fpmoney.Amount{}) { - t.Errorf("has to be 0 on error") - } - return - } - }) -} - -func FuzzToFloat(f *testing.F) { - tests := []int64{ - 0, - 1, - 123456, - } - for _, tc := range tests { - f.Add(tc) - f.Add(-tc) - } - f.Fuzz(func(t *testing.T, v int64) { - a := fpmoney.FromIntScaled(v, fpmoney.KRW) - - if float32(v) != a.Float32() { - t.Error(a, a.Float32(), float32(v)) - } - - if float64(v) != a.Float64() { - t.Error(a, a.Float64(), v) - } - }) -} - -func TestUnmarshalJSON(t *testing.T) { - tests := []struct { - s string - v fpmoney.Amount - }{ - // 2 cents - { - s: `{"currency": "SGD","amount": 9002.01}`, - v: fpmoney.FromIntScaled(900201, fpmoney.SGD), - }, - { - s: `{"amount": 9002.01, "currency": "SGD"}`, - v: fpmoney.FromIntScaled(900201, fpmoney.SGD), - }, - { - s: `{"amount": -9002.01, "currency": "SGD"}`, - v: fpmoney.FromIntScaled(-900201, fpmoney.SGD), - }, - { - s: `{"amount": 0, "currency": "SGD"}`, - v: fpmoney.FromIntScaled(0, fpmoney.SGD), - }, - { - s: `{"amount": 0.01, "currency": "SGD"}`, - v: fpmoney.FromIntScaled(1, fpmoney.SGD), - }, - { - s: `{"amount": -0.01, "currency": "SGD"}`, - v: fpmoney.FromIntScaled(-1, fpmoney.SGD), - }, - // 0 cents - { - s: `{"amount":1,"currency":"KRW"}`, - v: fpmoney.FromIntScaled(1, fpmoney.KRW), - }, - { - s: `{"amount":-1,"currency":"KRW"}`, - v: fpmoney.FromIntScaled(-1, fpmoney.KRW), - }, - { - s: `{"amount":123,"currency":"KRW"}`, - v: fpmoney.FromIntScaled(123, fpmoney.KRW), - }, - { - s: `{"amount":-123,"currency":"KRW"}`, - v: fpmoney.FromIntScaled(-123, fpmoney.KRW), - }, - // 2 cents strange valid input - { - s: ` { "amount" : 9002.01 - - , - - "currency" - : - - "SGD"} - - - `, - v: fpmoney.FromIntScaled(900201, fpmoney.SGD), - }, - } - for _, tc := range tests { - t.Run(tc.s, func(t *testing.T) { - var v fpmoney.Amount - err := json.Unmarshal([]byte(tc.s), &v) - if err != nil { - t.Error(err) - } - if tc.v != v { - t.Error(tc.v, v) - } - }) - } -} - -func TestMarshalJSON(t *testing.T) { - tests := []struct { - s string - v fpmoney.Amount - }{ - // 2 cents - { - s: `{"amount":9002.01,"currency":"SGD"}`, - v: fpmoney.FromIntScaled(900201, fpmoney.SGD), - }, - { - s: `{"amount":0,"currency":"SGD"}`, - v: fpmoney.FromIntScaled(0, fpmoney.SGD), - }, - { - s: `{"amount":0.01,"currency":"SGD"}`, - v: fpmoney.FromIntScaled(1, fpmoney.SGD), - }, - { - s: `{"amount":-0.01,"currency":"SGD"}`, - v: fpmoney.FromIntScaled(-1, fpmoney.SGD), - }, - { - s: `{"amount":1.01,"currency":"SGD"}`, - v: fpmoney.FromIntScaled(101, fpmoney.SGD), - }, - { - s: `{"amount":-1.01,"currency":"SGD"}`, - v: fpmoney.FromIntScaled(-101, fpmoney.SGD), - }, - { - s: `{"amount":1,"currency":"SGD"}`, - v: fpmoney.FromInt(1, fpmoney.SGD), - }, - { - s: `{"amount":5,"currency":"SGD"}`, - v: fpmoney.FromInt(5, fpmoney.SGD), - }, - { - s: `{"amount":-1,"currency":"SGD"}`, - v: fpmoney.FromInt(-1, fpmoney.SGD), - }, - { - s: `{"amount":-5,"currency":"SGD"}`, - v: fpmoney.FromInt(-5, fpmoney.SGD), - }, - // 0 cents - { - s: `{"amount":1,"currency":"KRW"}`, - v: fpmoney.FromIntScaled(1, fpmoney.KRW), - }, - { - s: `{"amount":-1,"currency":"KRW"}`, - v: fpmoney.FromIntScaled(-1, fpmoney.KRW), - }, - { - s: `{"amount":123,"currency":"KRW"}`, - v: fpmoney.FromIntScaled(123, fpmoney.KRW), - }, - { - s: `{"amount":-123,"currency":"KRW"}`, - v: fpmoney.FromIntScaled(-123, fpmoney.KRW), - }, - } - for _, tc := range tests { - t.Run(tc.s, func(t *testing.T) { - s, err := json.Marshal(tc.v) - if err != nil { - t.Error(err) - } - if tc.s != string(s) { - t.Error(tc.s, string(s)) - } - }) - } -} - func FuzzJSON_MarshalUnmarshal(f *testing.F) { currencies := [...]fpmoney.Currency{ fpmoney.KRW, @@ -483,7 +188,7 @@ func FuzzJSON_MarshalUnmarshal(f *testing.F) { } currency := currencies[c] - q := fpmoney.FromIntScaled(v, currency) + q := fpmoney.Amount{Amount: fpdecimal.FromIntScaled(v), Currency: currency} s, err := json.Marshal(q) if err != nil { @@ -502,8 +207,8 @@ func FuzzJSON_MarshalUnmarshal(f *testing.F) { } func BenchmarkArithmetic(b *testing.B) { - x := fpmoney.FromFloat(12312.31, fpmoney.SGD) - y := fpmoney.FromFloat(12.02, fpmoney.SGD) + x := fpmoney.Amount{Amount: fpdecimal.FromFloat(12312.31), Currency: fpmoney.SGD} + y := fpmoney.Amount{Amount: fpdecimal.FromFloat(12.02), Currency: fpmoney.SGD} b.ResetTimer() b.Run("add_x1", func(b *testing.B) { @@ -521,7 +226,7 @@ func BenchmarkArithmetic(b *testing.B) { } }) - if x == fpmoney.FromInt(0, fpmoney.SGD) { + if x == (fpmoney.Amount{Amount: fpdecimal.Zero, Currency: fpmoney.SGD}) { b.Error() } } @@ -551,8 +256,8 @@ func BenchmarkJSONUnmarshal(b *testing.B) { for _, tc := range testsFloats { b.Run(tc.name, func(b *testing.B) { for n := 0; n < b.N; n++ { - err := json.Unmarshal([]byte(tc.vals[n%len(tc.vals)]), &s) - if err != nil || s == fpmoney.FromInt(0, fpmoney.SGD) { + v := fpmoney.Amount{Amount: fpdecimal.Zero, Currency: fpmoney.SGD} + if err := json.Unmarshal([]byte(tc.vals[n%len(tc.vals)]), &s); err != nil || s == v { b.Error(s, err) } } @@ -606,7 +311,7 @@ func BenchmarkJSONMarshal_Exact(b *testing.B) { b.ResetTimer() b.Run(tc.name, func(b *testing.B) { for n := 0; n < b.N; n++ { - s, err = tests[n%len(tc.vals)].MarshalJSON() + s, err = json.Marshal(tests[n%len(tc.vals)]) if err != nil { b.Error(err) } @@ -626,38 +331,38 @@ func TestArithmetic_WrongCurrency(t *testing.T) { e *fpmoney.ErrCurrencyMismatch }{ { - a: fpmoney.FromInt(10, fpmoney.SGD), - b: fpmoney.FromInt(11, fpmoney.USD), + a: fpmoney.Amount{Amount: fpdecimal.FromInt(10), Currency: fpmoney.SGD}, + b: fpmoney.Amount{Amount: fpdecimal.FromInt(11), Currency: fpmoney.USD}, f: func(a, b fpmoney.Amount) { a.Add(b) }, e: &fpmoney.ErrCurrencyMismatch{A: fpmoney.SGD, B: fpmoney.USD}, }, { - a: fpmoney.FromInt(10, fpmoney.SGD), - b: fpmoney.FromInt(11, fpmoney.USD), + a: fpmoney.Amount{Amount: fpdecimal.FromInt(10), Currency: fpmoney.SGD}, + b: fpmoney.Amount{Amount: fpdecimal.FromInt(11), Currency: fpmoney.USD}, f: func(a, b fpmoney.Amount) { a.Sub(b) }, e: &fpmoney.ErrCurrencyMismatch{A: fpmoney.SGD, B: fpmoney.USD}, }, { - a: fpmoney.FromInt(10, fpmoney.SGD), - b: fpmoney.FromInt(11, fpmoney.USD), + a: fpmoney.Amount{Amount: fpdecimal.FromInt(10), Currency: fpmoney.SGD}, + b: fpmoney.Amount{Amount: fpdecimal.FromInt(11), Currency: fpmoney.USD}, f: func(a, b fpmoney.Amount) { a.LessThan(b) }, e: &fpmoney.ErrCurrencyMismatch{A: fpmoney.SGD, B: fpmoney.USD}, }, { - a: fpmoney.FromInt(10, fpmoney.SGD), - b: fpmoney.FromInt(11, fpmoney.USD), + a: fpmoney.Amount{Amount: fpdecimal.FromInt(10), Currency: fpmoney.SGD}, + b: fpmoney.Amount{Amount: fpdecimal.FromInt(11), Currency: fpmoney.USD}, f: func(a, b fpmoney.Amount) { a.LessThanOrEqual(b) }, e: &fpmoney.ErrCurrencyMismatch{A: fpmoney.SGD, B: fpmoney.USD}, }, { - a: fpmoney.FromInt(10, fpmoney.SGD), - b: fpmoney.FromInt(11, fpmoney.USD), + a: fpmoney.Amount{Amount: fpdecimal.FromInt(10), Currency: fpmoney.SGD}, + b: fpmoney.Amount{Amount: fpdecimal.FromInt(11), Currency: fpmoney.USD}, f: func(a, b fpmoney.Amount) { a.GreaterThan(b) }, e: &fpmoney.ErrCurrencyMismatch{A: fpmoney.SGD, B: fpmoney.USD}, }, { - a: fpmoney.FromInt(10, fpmoney.SGD), - b: fpmoney.FromInt(11, fpmoney.USD), + a: fpmoney.Amount{Amount: fpdecimal.FromInt(10), Currency: fpmoney.SGD}, + b: fpmoney.Amount{Amount: fpdecimal.FromInt(11), Currency: fpmoney.USD}, f: func(a, b fpmoney.Amount) { a.GreaterThanOrEqual(b) }, e: &fpmoney.ErrCurrencyMismatch{A: fpmoney.SGD, B: fpmoney.USD}, }, diff --git a/currency.go b/currency.go index 9d66944..9444505 100644 --- a/currency.go +++ b/currency.go @@ -1,48 +1,191 @@ package fpmoney +//go:generate go run github.com/nikolaydubina/go-enum-encoding@latest -type=Currency + // Currency is ISO 4217 without deprecated currencies. -// Zero value is undefined currency. type Currency struct{ v uint8 } -// Alpha returns the ISO 4217 three-letter alphabetic code. -func (c Currency) Alpha() string { return currencies[c].alpha } - -// Exponent returns the decimal point location. -func (c Currency) Exponent() uint8 { return currencies[c].exponent } - -func (c Currency) String() string { return c.Alpha() } - -func (c Currency) IsUndefined() bool { return c.v == 0 } - -func (c *Currency) UnmarshalText(text []byte) error { - v, ok := fromAlpha[string(text)] - if !ok { - return &ErrWrongCurrencyString{} - } - *c = v - return nil -} - -func (c Currency) MarshalText() (text []byte, err error) { return []byte(currencies[c].alpha), nil } - -// CurrencyFromAlpha returns Currency for the three-letter alpha code. -// Or an error if it does not exist. -func CurrencyFromAlpha(alpha string) (Currency, error) { - if c, ok := fromAlpha[alpha]; ok { - return c, nil - } - return Currency{}, &ErrWrongCurrencyString{} -} - -func (c Currency) scale() int64 { - switch c.Exponent() { - case 4: - return 10000 - case 3: - return 1000 - case 2: - return 100 - default: - return 1 - } -} +var ( + Undefined = Currency{} // json:"" + AED = Currency{1} // json:"AED" + AFN = Currency{2} // json:"AFN" + ALL = Currency{3} // json:"ALL" + AMD = Currency{4} // json:"AMD" + ANG = Currency{5} // json:"ANG" + AOA = Currency{6} // json:"AOA" + ARS = Currency{7} // json:"ARS" + AUD = Currency{8} // json:"AUD" + AWG = Currency{9} // json:"AWG" + AZN = Currency{10} // json:"AZN" + BAM = Currency{11} // json:"BAM" + BBD = Currency{12} // json:"BBD" + BDT = Currency{13} // json:"BDT" + BGN = Currency{14} // json:"BGN" + BHD = Currency{15} // json:"BHD" + BIF = Currency{16} // json:"BIF" + BMD = Currency{17} // json:"BMD" + BND = Currency{18} // json:"BND" + BOB = Currency{19} // json:"BOB" + BOV = Currency{20} // json:"BOV" + BRL = Currency{21} // json:"BRL" + BSD = Currency{22} // json:"BSD" + BTN = Currency{23} // json:"BTN" + BWP = Currency{24} // json:"BWP" + BYN = Currency{25} // json:"BYN" + BZD = Currency{26} // json:"BZD" + CAD = Currency{27} // json:"CAD" + CDF = Currency{28} // json:"CDF" + CHE = Currency{29} // json:"CHE" + CHF = Currency{30} // json:"CHF" + CHW = Currency{31} // json:"CHW" + CLF = Currency{32} // json:"CLF" + CLP = Currency{33} // json:"CLP" + CNY = Currency{34} // json:"CNY" + COP = Currency{35} // json:"COP" + COU = Currency{36} // json:"COU" + CRC = Currency{37} // json:"CRC" + CUP = Currency{38} // json:"CUP" + CVE = Currency{39} // json:"CVE" + CZK = Currency{40} // json:"CZK" + DJF = Currency{41} // json:"DJF" + DKK = Currency{42} // json:"DKK" + DOP = Currency{43} // json:"DOP" + DZD = Currency{44} // json:"DZD" + EGP = Currency{45} // json:"EGP" + ERN = Currency{46} // json:"ERN" + ETB = Currency{47} // json:"ETB" + EUR = Currency{48} // json:"EUR" + FJD = Currency{49} // json:"FJD" + FKP = Currency{50} // json:"FKP" + GBP = Currency{51} // json:"GBP" + GEL = Currency{52} // json:"GEL" + GHS = Currency{53} // json:"GHS" + GIP = Currency{54} // json:"GIP" + GMD = Currency{55} // json:"GMD" + GNF = Currency{56} // json:"GNF" + GTQ = Currency{57} // json:"GTQ" + GYD = Currency{58} // json:"GYD" + HKD = Currency{59} // json:"HKD" + HNL = Currency{60} // json:"HNL" + HRD = Currency{61} // json:"HRD" + HRK = Currency{62} // json:"HRK" + HTG = Currency{63} // json:"HTG" + HUF = Currency{64} // json:"HUF" + IDR = Currency{65} // json:"IDR" + ILS = Currency{66} // json:"ILS" + INR = Currency{67} // json:"INR" + IQD = Currency{68} // json:"IQD" + IRR = Currency{69} // json:"IRR" + ISK = Currency{70} // json:"ISK" + JMD = Currency{71} // json:"JMD" + JOD = Currency{72} // json:"JOD" + JPY = Currency{73} // json:"JPY" + KES = Currency{74} // json:"KES" + KGS = Currency{75} // json:"KGS" + KHR = Currency{76} // json:"KHR" + KMF = Currency{77} // json:"KMF" + KPW = Currency{78} // json:"KPW" + KRW = Currency{79} // json:"KRW" + KWD = Currency{80} // json:"KWD" + KYD = Currency{81} // json:"KYD" + KZT = Currency{82} // json:"KZT" + LAK = Currency{83} // json:"LAK" + LBP = Currency{84} // json:"LBP" + LKR = Currency{85} // json:"LKR" + LRD = Currency{86} // json:"LRD" + LSL = Currency{87} // json:"LSL" + LYD = Currency{88} // json:"LYD" + MAD = Currency{89} // json:"MAD" + MDL = Currency{90} // json:"MDL" + MGA = Currency{91} // json:"MGA" + MKD = Currency{92} // json:"MKD" + MMK = Currency{93} // json:"MMK" + MNT = Currency{94} // json:"MNT" + MOP = Currency{95} // json:"MOP" + MRU = Currency{96} // json:"MRU" + MUR = Currency{97} // json:"MUR" + MVR = Currency{98} // json:"MVR" + MWK = Currency{99} // json:"MWK" + MXN = Currency{100} // json:"MXN" + MXV = Currency{101} // json:"MXV" + MYR = Currency{102} // json:"MYR" + MZN = Currency{103} // json:"MZN" + NAD = Currency{104} // json:"NAD" + NGN = Currency{105} // json:"NGN" + NIO = Currency{106} // json:"NIO" + NOK = Currency{107} // json:"NOK" + NPR = Currency{108} // json:"NPR" + NZD = Currency{109} // json:"NZD" + OMR = Currency{110} // json:"OMR" + PAB = Currency{111} // json:"PAB" + PEN = Currency{112} // json:"PEN" + PGK = Currency{113} // json:"PGK" + PHP = Currency{114} // json:"PHP" + PKR = Currency{115} // json:"PKR" + PLN = Currency{116} // json:"PLN" + PYG = Currency{117} // json:"PYG" + QAR = Currency{118} // json:"QAR" + RON = Currency{119} // json:"RON" + RSD = Currency{120} // json:"RSD" + RUB = Currency{121} // json:"RUB" + RWF = Currency{122} // json:"RWF" + SAR = Currency{123} // json:"SAR" + SBD = Currency{124} // json:"SBD" + SCR = Currency{125} // json:"SCR" + SDG = Currency{126} // json:"SDG" + SEK = Currency{127} // json:"SEK" + SGD = Currency{128} // json:"SGD" + SHP = Currency{129} // json:"SHP" + SLE = Currency{130} // json:"SLE" + SLL = Currency{131} // json:"SLL" + SOS = Currency{132} // json:"SOS" + SRD = Currency{133} // json:"SRD" + SSP = Currency{134} // json:"SSP" + STN = Currency{135} // json:"STN" + SVC = Currency{136} // json:"SVC" + SYP = Currency{137} // json:"SYP" + SZL = Currency{138} // json:"SZL" + THB = Currency{139} // json:"THB" + TJS = Currency{140} // json:"TJS" + TMT = Currency{141} // json:"TMT" + TND = Currency{142} // json:"TND" + TOP = Currency{143} // json:"TOP" + TRY = Currency{144} // json:"TRY" + TTD = Currency{145} // json:"TTD" + TWD = Currency{146} // json:"TWD" + TZS = Currency{147} // json:"TZS" + UAH = Currency{148} // json:"UAH" + UGX = Currency{149} // json:"UGX" + USD = Currency{150} // json:"USD" + USN = Currency{151} // json:"USN" + UYI = Currency{152} // json:"UYI" + UYU = Currency{153} // json:"UYU" + UYW = Currency{154} // json:"UYW" + UZS = Currency{155} // json:"UZS" + VED = Currency{156} // json:"VED" + VES = Currency{157} // json:"VES" + VND = Currency{158} // json:"VND" + VUV = Currency{159} // json:"VUV" + WST = Currency{160} // json:"WST" + XAF = Currency{161} // json:"XAF" + XAG = Currency{162} // json:"XAG" + XAU = Currency{163} // json:"XAU" + XBA = Currency{164} // json:"XBA" + XBB = Currency{165} // json:"XBB" + XBC = Currency{166} // json:"XBC" + XBD = Currency{167} // json:"XBD" + XCD = Currency{168} // json:"XCD" + XDR = Currency{169} // json:"XDR" + XOF = Currency{170} // json:"XOF" + XPD = Currency{171} // json:"XPD" + XPF = Currency{172} // json:"XPF" + XPT = Currency{173} // json:"XPT" + XSU = Currency{174} // json:"XSU" + XTS = Currency{175} // json:"XTS" + XUA = Currency{176} // json:"XUA" + XXX = Currency{177} // json:"XXX" + YER = Currency{178} // json:"YER" + ZAR = Currency{179} // json:"ZAR" + ZMW = Currency{180} // json:"ZMW" + ZWL = Currency{181} // json:"ZWL" +) diff --git a/currency_enum.go b/currency_enum.go deleted file mode 100644 index 7580506..0000000 --- a/currency_enum.go +++ /dev/null @@ -1,565 +0,0 @@ -package fpmoney - -const ( - _ uint8 = iota - iAED - iAFN - iALL - iAMD - iANG - iAOA - iARS - iAUD - iAWG - iAZN - iBAM - iBBD - iBDT - iBGN - iBHD - iBIF - iBMD - iBND - iBOB - iBOV - iBRL - iBSD - iBTN - iBWP - iBYN - iBZD - iCAD - iCDF - iCHE - iCHF - iCHW - iCLF - iCLP - iCNY - iCOP - iCOU - iCRC - iCUP - iCVE - iCZK - iDJF - iDKK - iDOP - iDZD - iEGP - iERN - iETB - iEUR - iFJD - iFKP - iGBP - iGEL - iGHS - iGIP - iGMD - iGNF - iGTQ - iGYD - iHKD - iHNL - iHRD - iHRK - iHTG - iHUF - iIDR - iILS - iINR - iIQD - iIRR - iISK - iJMD - iJOD - iJPY - iKES - iKGS - iKHR - iKMF - iKPW - iKRW - iKWD - iKYD - iKZT - iLAK - iLBP - iLKR - iLRD - iLSL - iLYD - iMAD - iMDL - iMGA - iMKD - iMMK - iMNT - iMOP - iMRU - iMUR - iMVR - iMWK - iMXN - iMXV - iMYR - iMZN - iNAD - iNGN - iNIO - iNOK - iNPR - iNZD - iOMR - iPAB - iPEN - iPGK - iPHP - iPKR - iPLN - iPYG - iQAR - iRON - iRSD - iRUB - iRWF - iSAR - iSBD - iSCR - iSDG - iSEK - iSGD - iSHP - iSLE - iSLL - iSOS - iSRD - iSSP - iSTN - iSVC - iSYP - iSZL - iTHB - iTJS - iTMT - iTND - iTOP - iTRY - iTTD - iTWD - iTZS - iUAH - iUGX - iUSD - iUSN - iUYI - iUYU - iUYW - iUZS - iVED - iVES - iVND - iVUV - iWST - iXAF - iXAG - iXAU - iXBA - iXBB - iXBC - iXBD - iXCD - iXDR - iXOF - iXPD - iXPF - iXPT - iXSU - iXTS - iXUA - iXXX - iYER - iZAR - iZMW - iZWL -) - -var ( - AED = Currency{iAED} - AFN = Currency{iAFN} - ALL = Currency{iALL} - AMD = Currency{iAMD} - ANG = Currency{iANG} - AOA = Currency{iAOA} - ARS = Currency{iARS} - AUD = Currency{iAUD} - AWG = Currency{iAWG} - AZN = Currency{iAZN} - BAM = Currency{iBAM} - BBD = Currency{iBBD} - BDT = Currency{iBDT} - BGN = Currency{iBGN} - BHD = Currency{iBHD} - BIF = Currency{iBIF} - BMD = Currency{iBMD} - BND = Currency{iBND} - BOB = Currency{iBOB} - BOV = Currency{iBOV} - BRL = Currency{iBRL} - BSD = Currency{iBSD} - BTN = Currency{iBTN} - BWP = Currency{iBWP} - BYN = Currency{iBYN} - BZD = Currency{iBZD} - CAD = Currency{iCAD} - CDF = Currency{iCDF} - CHE = Currency{iCHE} - CHF = Currency{iCHF} - CHW = Currency{iCHW} - CLF = Currency{iCLF} - CLP = Currency{iCLP} - CNY = Currency{iCNY} - COP = Currency{iCOP} - COU = Currency{iCOU} - CRC = Currency{iCRC} - CUP = Currency{iCUP} - CVE = Currency{iCVE} - CZK = Currency{iCZK} - DJF = Currency{iDJF} - DKK = Currency{iDKK} - DOP = Currency{iDOP} - DZD = Currency{iDZD} - EGP = Currency{iEGP} - ERN = Currency{iERN} - ETB = Currency{iETB} - EUR = Currency{iEUR} - FJD = Currency{iFJD} - FKP = Currency{iFKP} - GBP = Currency{iGBP} - GEL = Currency{iGEL} - GHS = Currency{iGHS} - GIP = Currency{iGIP} - GMD = Currency{iGMD} - GNF = Currency{iGNF} - GTQ = Currency{iGTQ} - GYD = Currency{iGYD} - HKD = Currency{iHKD} - HNL = Currency{iHNL} - HRD = Currency{iHRD} - HRK = Currency{iHRK} - HTG = Currency{iHTG} - HUF = Currency{iHUF} - IDR = Currency{iIDR} - ILS = Currency{iILS} - INR = Currency{iINR} - IQD = Currency{iIQD} - IRR = Currency{iIRR} - ISK = Currency{iISK} - JMD = Currency{iJMD} - JOD = Currency{iJOD} - JPY = Currency{iJPY} - KES = Currency{iKES} - KGS = Currency{iKGS} - KHR = Currency{iKHR} - KMF = Currency{iKMF} - KPW = Currency{iKPW} - KRW = Currency{iKRW} - KWD = Currency{iKWD} - KYD = Currency{iKYD} - KZT = Currency{iKZT} - LAK = Currency{iLAK} - LBP = Currency{iLBP} - LKR = Currency{iLKR} - LRD = Currency{iLRD} - LSL = Currency{iLSL} - LYD = Currency{iLYD} - MAD = Currency{iMAD} - MDL = Currency{iMDL} - MGA = Currency{iMGA} - MKD = Currency{iMKD} - MMK = Currency{iMMK} - MNT = Currency{iMNT} - MOP = Currency{iMOP} - MRU = Currency{iMRU} - MUR = Currency{iMUR} - MVR = Currency{iMVR} - MWK = Currency{iMWK} - MXN = Currency{iMXN} - MXV = Currency{iMXV} - MYR = Currency{iMYR} - MZN = Currency{iMZN} - NAD = Currency{iNAD} - NGN = Currency{iNGN} - NIO = Currency{iNIO} - NOK = Currency{iNOK} - NPR = Currency{iNPR} - NZD = Currency{iNZD} - OMR = Currency{iOMR} - PAB = Currency{iPAB} - PEN = Currency{iPEN} - PGK = Currency{iPGK} - PHP = Currency{iPHP} - PKR = Currency{iPKR} - PLN = Currency{iPLN} - PYG = Currency{iPYG} - QAR = Currency{iQAR} - RON = Currency{iRON} - RSD = Currency{iRSD} - RUB = Currency{iRUB} - RWF = Currency{iRWF} - SAR = Currency{iSAR} - SBD = Currency{iSBD} - SCR = Currency{iSCR} - SDG = Currency{iSDG} - SEK = Currency{iSEK} - SGD = Currency{iSGD} - SHP = Currency{iSHP} - SLE = Currency{iSLE} - SLL = Currency{iSLL} - SOS = Currency{iSOS} - SRD = Currency{iSRD} - SSP = Currency{iSSP} - STN = Currency{iSTN} - SVC = Currency{iSVC} - SYP = Currency{iSYP} - SZL = Currency{iSZL} - THB = Currency{iTHB} - TJS = Currency{iTJS} - TMT = Currency{iTMT} - TND = Currency{iTND} - TOP = Currency{iTOP} - TRY = Currency{iTRY} - TTD = Currency{iTTD} - TWD = Currency{iTWD} - TZS = Currency{iTZS} - UAH = Currency{iUAH} - UGX = Currency{iUGX} - USD = Currency{iUSD} - USN = Currency{iUSN} - UYI = Currency{iUYI} - UYU = Currency{iUYU} - UYW = Currency{iUYW} - UZS = Currency{iUZS} - VED = Currency{iVED} - VES = Currency{iVES} - VND = Currency{iVND} - VUV = Currency{iVUV} - WST = Currency{iWST} - XAF = Currency{iXAF} - XAG = Currency{iXAG} - XAU = Currency{iXAU} - XBA = Currency{iXBA} - XBB = Currency{iXBB} - XBC = Currency{iXBC} - XBD = Currency{iXBD} - XCD = Currency{iXCD} - XDR = Currency{iXDR} - XOF = Currency{iXOF} - XPD = Currency{iXPD} - XPF = Currency{iXPF} - XPT = Currency{iXPT} - XSU = Currency{iXSU} - XTS = Currency{iXTS} - XUA = Currency{iXUA} - XXX = Currency{iXXX} - YER = Currency{iYER} - ZAR = Currency{iZAR} - ZMW = Currency{iZMW} - ZWL = Currency{iZWL} -) - -var currencies = map[Currency]struct { - alpha string - exponent uint8 -}{ - AED: {alpha: "AED", exponent: 2}, - AFN: {alpha: "AFN", exponent: 2}, - ALL: {alpha: "ALL", exponent: 2}, - AMD: {alpha: "AMD", exponent: 2}, - ANG: {alpha: "ANG", exponent: 2}, - AOA: {alpha: "AOA", exponent: 2}, - ARS: {alpha: "ARS", exponent: 2}, - AUD: {alpha: "AUD", exponent: 2}, - AWG: {alpha: "AWG", exponent: 2}, - AZN: {alpha: "AZN", exponent: 2}, - BAM: {alpha: "BAM", exponent: 2}, - BBD: {alpha: "BBD", exponent: 2}, - BDT: {alpha: "BDT", exponent: 2}, - BGN: {alpha: "BGN", exponent: 2}, - BHD: {alpha: "BHD", exponent: 3}, - BIF: {alpha: "BIF", exponent: 0}, - BMD: {alpha: "BMD", exponent: 2}, - BND: {alpha: "BND", exponent: 2}, - BOB: {alpha: "BOB", exponent: 2}, - BOV: {alpha: "BOV", exponent: 2}, - BRL: {alpha: "BRL", exponent: 2}, - BSD: {alpha: "BSD", exponent: 2}, - BTN: {alpha: "BTN", exponent: 2}, - BWP: {alpha: "BWP", exponent: 2}, - BYN: {alpha: "BYN", exponent: 2}, - BZD: {alpha: "BZD", exponent: 2}, - CAD: {alpha: "CAD", exponent: 2}, - CDF: {alpha: "CDF", exponent: 2}, - CHE: {alpha: "CHE", exponent: 2}, - CHF: {alpha: "CHF", exponent: 2}, - CHW: {alpha: "CHW", exponent: 2}, - CLF: {alpha: "CLF", exponent: 4}, - CLP: {alpha: "CLP", exponent: 0}, - CNY: {alpha: "CNY", exponent: 2}, - COP: {alpha: "COP", exponent: 2}, - COU: {alpha: "COU", exponent: 2}, - CRC: {alpha: "CRC", exponent: 2}, - CUP: {alpha: "CUP", exponent: 2}, - CVE: {alpha: "CVE", exponent: 2}, - CZK: {alpha: "CZK", exponent: 2}, - DJF: {alpha: "DJF", exponent: 0}, - DKK: {alpha: "DKK", exponent: 2}, - DOP: {alpha: "DOP", exponent: 2}, - DZD: {alpha: "DZD", exponent: 2}, - EGP: {alpha: "EGP", exponent: 2}, - ERN: {alpha: "ERN", exponent: 2}, - ETB: {alpha: "ETB", exponent: 2}, - EUR: {alpha: "EUR", exponent: 2}, - FJD: {alpha: "FJD", exponent: 2}, - FKP: {alpha: "FKP", exponent: 2}, - GBP: {alpha: "GBP", exponent: 2}, - GEL: {alpha: "GEL", exponent: 2}, - GHS: {alpha: "GHS", exponent: 2}, - GIP: {alpha: "GIP", exponent: 2}, - GMD: {alpha: "GMD", exponent: 2}, - GNF: {alpha: "GNF", exponent: 0}, - GTQ: {alpha: "GTQ", exponent: 2}, - GYD: {alpha: "GYD", exponent: 2}, - HKD: {alpha: "HKD", exponent: 2}, - HNL: {alpha: "HNL", exponent: 2}, - HRD: {alpha: "HRD", exponent: 0}, - HRK: {alpha: "HRK", exponent: 2}, - HTG: {alpha: "HTG", exponent: 2}, - HUF: {alpha: "HUF", exponent: 2}, - IDR: {alpha: "IDR", exponent: 2}, - ILS: {alpha: "ILS", exponent: 2}, - INR: {alpha: "INR", exponent: 2}, - IQD: {alpha: "IQD", exponent: 3}, - IRR: {alpha: "IRR", exponent: 2}, - ISK: {alpha: "ISK", exponent: 0}, - JMD: {alpha: "JMD", exponent: 2}, - JOD: {alpha: "JOD", exponent: 3}, - JPY: {alpha: "JPY", exponent: 0}, - KES: {alpha: "KES", exponent: 2}, - KGS: {alpha: "KGS", exponent: 2}, - KHR: {alpha: "KHR", exponent: 2}, - KMF: {alpha: "KMF", exponent: 0}, - KPW: {alpha: "KPW", exponent: 2}, - KRW: {alpha: "KRW", exponent: 0}, - KWD: {alpha: "KWD", exponent: 3}, - KYD: {alpha: "KYD", exponent: 2}, - KZT: {alpha: "KZT", exponent: 2}, - LAK: {alpha: "LAK", exponent: 2}, - LBP: {alpha: "LBP", exponent: 2}, - LKR: {alpha: "LKR", exponent: 2}, - LRD: {alpha: "LRD", exponent: 2}, - LSL: {alpha: "LSL", exponent: 2}, - LYD: {alpha: "LYD", exponent: 3}, - MAD: {alpha: "MAD", exponent: 2}, - MDL: {alpha: "MDL", exponent: 2}, - MGA: {alpha: "MGA", exponent: 2}, - MKD: {alpha: "MKD", exponent: 2}, - MMK: {alpha: "MMK", exponent: 2}, - MNT: {alpha: "MNT", exponent: 2}, - MOP: {alpha: "MOP", exponent: 2}, - MRU: {alpha: "MRU", exponent: 2}, - MUR: {alpha: "MUR", exponent: 2}, - MVR: {alpha: "MVR", exponent: 2}, - MWK: {alpha: "MWK", exponent: 2}, - MXN: {alpha: "MXN", exponent: 2}, - MXV: {alpha: "MXV", exponent: 2}, - MYR: {alpha: "MYR", exponent: 2}, - MZN: {alpha: "MZN", exponent: 2}, - NAD: {alpha: "NAD", exponent: 2}, - NGN: {alpha: "NGN", exponent: 2}, - NIO: {alpha: "NIO", exponent: 2}, - NOK: {alpha: "NOK", exponent: 2}, - NPR: {alpha: "NPR", exponent: 2}, - NZD: {alpha: "NZD", exponent: 2}, - OMR: {alpha: "OMR", exponent: 3}, - PAB: {alpha: "PAB", exponent: 2}, - PEN: {alpha: "PEN", exponent: 2}, - PGK: {alpha: "PGK", exponent: 2}, - PHP: {alpha: "PHP", exponent: 2}, - PKR: {alpha: "PKR", exponent: 2}, - PLN: {alpha: "PLN", exponent: 2}, - PYG: {alpha: "PYG", exponent: 0}, - QAR: {alpha: "QAR", exponent: 2}, - RON: {alpha: "RON", exponent: 2}, - RSD: {alpha: "RSD", exponent: 2}, - RUB: {alpha: "RUB", exponent: 2}, - RWF: {alpha: "RWF", exponent: 0}, - SAR: {alpha: "SAR", exponent: 2}, - SBD: {alpha: "SBD", exponent: 2}, - SCR: {alpha: "SCR", exponent: 2}, - SDG: {alpha: "SDG", exponent: 2}, - SEK: {alpha: "SEK", exponent: 2}, - SGD: {alpha: "SGD", exponent: 2}, - SHP: {alpha: "SHP", exponent: 2}, - SLE: {alpha: "SLE", exponent: 2}, - SLL: {alpha: "SLL", exponent: 2}, - SOS: {alpha: "SOS", exponent: 2}, - SRD: {alpha: "SRD", exponent: 2}, - SSP: {alpha: "SSP", exponent: 2}, - STN: {alpha: "STN", exponent: 2}, - SVC: {alpha: "SVC", exponent: 2}, - SYP: {alpha: "SYP", exponent: 2}, - SZL: {alpha: "SZL", exponent: 2}, - THB: {alpha: "THB", exponent: 2}, - TJS: {alpha: "TJS", exponent: 2}, - TMT: {alpha: "TMT", exponent: 2}, - TND: {alpha: "TND", exponent: 3}, - TOP: {alpha: "TOP", exponent: 2}, - TRY: {alpha: "TRY", exponent: 2}, - TTD: {alpha: "TTD", exponent: 2}, - TWD: {alpha: "TWD", exponent: 2}, - TZS: {alpha: "TZS", exponent: 2}, - UAH: {alpha: "UAH", exponent: 2}, - UGX: {alpha: "UGX", exponent: 0}, - USD: {alpha: "USD", exponent: 2}, - USN: {alpha: "USN", exponent: 2}, - UYI: {alpha: "UYI", exponent: 0}, - UYU: {alpha: "UYU", exponent: 2}, - UYW: {alpha: "UYW", exponent: 4}, - UZS: {alpha: "UZS", exponent: 2}, - VED: {alpha: "VED", exponent: 2}, - VES: {alpha: "VES", exponent: 2}, - VND: {alpha: "VND", exponent: 0}, - VUV: {alpha: "VUV", exponent: 0}, - WST: {alpha: "WST", exponent: 2}, - XAF: {alpha: "XAF", exponent: 0}, - XAG: {alpha: "XAG", exponent: 0}, - XAU: {alpha: "XAU", exponent: 0}, - XBA: {alpha: "XBA", exponent: 0}, - XBB: {alpha: "XBB", exponent: 0}, - XBC: {alpha: "XBC", exponent: 0}, - XBD: {alpha: "XBD", exponent: 0}, - XCD: {alpha: "XCD", exponent: 2}, - XDR: {alpha: "XDR", exponent: 0}, - XOF: {alpha: "XOF", exponent: 0}, - XPD: {alpha: "XPD", exponent: 0}, - XPF: {alpha: "XPF", exponent: 0}, - XPT: {alpha: "XPT", exponent: 0}, - XSU: {alpha: "XSU", exponent: 0}, - XTS: {alpha: "XTS", exponent: 0}, - XUA: {alpha: "XUA", exponent: 0}, - XXX: {alpha: "XXX", exponent: 0}, - YER: {alpha: "YER", exponent: 2}, - ZAR: {alpha: "ZAR", exponent: 2}, - ZMW: {alpha: "ZMW", exponent: 2}, - ZWL: {alpha: "ZWL", exponent: 2}, -} - -var fromAlpha = make(map[string]Currency, len(currencies)) - -func init() { - for c, v := range currencies { - fromAlpha[v.alpha] = c - } -} diff --git a/currency_enum_encoding.go b/currency_enum_encoding.go new file mode 100644 index 0000000..0037081 --- /dev/null +++ b/currency_enum_encoding.go @@ -0,0 +1,386 @@ +// Code generated by go-enum-encoding DO NOT EDIT +package fpmoney + +import "errors" + +var ErrUnknownCurrency = errors.New("unknown Currency") + +var vals_Currency = map[Currency][]byte{ + Undefined: []byte(""), + AED: []byte("AED"), + AFN: []byte("AFN"), + ALL: []byte("ALL"), + AMD: []byte("AMD"), + ANG: []byte("ANG"), + AOA: []byte("AOA"), + ARS: []byte("ARS"), + AUD: []byte("AUD"), + AWG: []byte("AWG"), + AZN: []byte("AZN"), + BAM: []byte("BAM"), + BBD: []byte("BBD"), + BDT: []byte("BDT"), + BGN: []byte("BGN"), + BHD: []byte("BHD"), + BIF: []byte("BIF"), + BMD: []byte("BMD"), + BND: []byte("BND"), + BOB: []byte("BOB"), + BOV: []byte("BOV"), + BRL: []byte("BRL"), + BSD: []byte("BSD"), + BTN: []byte("BTN"), + BWP: []byte("BWP"), + BYN: []byte("BYN"), + BZD: []byte("BZD"), + CAD: []byte("CAD"), + CDF: []byte("CDF"), + CHE: []byte("CHE"), + CHF: []byte("CHF"), + CHW: []byte("CHW"), + CLF: []byte("CLF"), + CLP: []byte("CLP"), + CNY: []byte("CNY"), + COP: []byte("COP"), + COU: []byte("COU"), + CRC: []byte("CRC"), + CUP: []byte("CUP"), + CVE: []byte("CVE"), + CZK: []byte("CZK"), + DJF: []byte("DJF"), + DKK: []byte("DKK"), + DOP: []byte("DOP"), + DZD: []byte("DZD"), + EGP: []byte("EGP"), + ERN: []byte("ERN"), + ETB: []byte("ETB"), + EUR: []byte("EUR"), + FJD: []byte("FJD"), + FKP: []byte("FKP"), + GBP: []byte("GBP"), + GEL: []byte("GEL"), + GHS: []byte("GHS"), + GIP: []byte("GIP"), + GMD: []byte("GMD"), + GNF: []byte("GNF"), + GTQ: []byte("GTQ"), + GYD: []byte("GYD"), + HKD: []byte("HKD"), + HNL: []byte("HNL"), + HRD: []byte("HRD"), + HRK: []byte("HRK"), + HTG: []byte("HTG"), + HUF: []byte("HUF"), + IDR: []byte("IDR"), + ILS: []byte("ILS"), + INR: []byte("INR"), + IQD: []byte("IQD"), + IRR: []byte("IRR"), + ISK: []byte("ISK"), + JMD: []byte("JMD"), + JOD: []byte("JOD"), + JPY: []byte("JPY"), + KES: []byte("KES"), + KGS: []byte("KGS"), + KHR: []byte("KHR"), + KMF: []byte("KMF"), + KPW: []byte("KPW"), + KRW: []byte("KRW"), + KWD: []byte("KWD"), + KYD: []byte("KYD"), + KZT: []byte("KZT"), + LAK: []byte("LAK"), + LBP: []byte("LBP"), + LKR: []byte("LKR"), + LRD: []byte("LRD"), + LSL: []byte("LSL"), + LYD: []byte("LYD"), + MAD: []byte("MAD"), + MDL: []byte("MDL"), + MGA: []byte("MGA"), + MKD: []byte("MKD"), + MMK: []byte("MMK"), + MNT: []byte("MNT"), + MOP: []byte("MOP"), + MRU: []byte("MRU"), + MUR: []byte("MUR"), + MVR: []byte("MVR"), + MWK: []byte("MWK"), + MXN: []byte("MXN"), + MXV: []byte("MXV"), + MYR: []byte("MYR"), + MZN: []byte("MZN"), + NAD: []byte("NAD"), + NGN: []byte("NGN"), + NIO: []byte("NIO"), + NOK: []byte("NOK"), + NPR: []byte("NPR"), + NZD: []byte("NZD"), + OMR: []byte("OMR"), + PAB: []byte("PAB"), + PEN: []byte("PEN"), + PGK: []byte("PGK"), + PHP: []byte("PHP"), + PKR: []byte("PKR"), + PLN: []byte("PLN"), + PYG: []byte("PYG"), + QAR: []byte("QAR"), + RON: []byte("RON"), + RSD: []byte("RSD"), + RUB: []byte("RUB"), + RWF: []byte("RWF"), + SAR: []byte("SAR"), + SBD: []byte("SBD"), + SCR: []byte("SCR"), + SDG: []byte("SDG"), + SEK: []byte("SEK"), + SGD: []byte("SGD"), + SHP: []byte("SHP"), + SLE: []byte("SLE"), + SLL: []byte("SLL"), + SOS: []byte("SOS"), + SRD: []byte("SRD"), + SSP: []byte("SSP"), + STN: []byte("STN"), + SVC: []byte("SVC"), + SYP: []byte("SYP"), + SZL: []byte("SZL"), + THB: []byte("THB"), + TJS: []byte("TJS"), + TMT: []byte("TMT"), + TND: []byte("TND"), + TOP: []byte("TOP"), + TRY: []byte("TRY"), + TTD: []byte("TTD"), + TWD: []byte("TWD"), + TZS: []byte("TZS"), + UAH: []byte("UAH"), + UGX: []byte("UGX"), + USD: []byte("USD"), + USN: []byte("USN"), + UYI: []byte("UYI"), + UYU: []byte("UYU"), + UYW: []byte("UYW"), + UZS: []byte("UZS"), + VED: []byte("VED"), + VES: []byte("VES"), + VND: []byte("VND"), + VUV: []byte("VUV"), + WST: []byte("WST"), + XAF: []byte("XAF"), + XAG: []byte("XAG"), + XAU: []byte("XAU"), + XBA: []byte("XBA"), + XBB: []byte("XBB"), + XBC: []byte("XBC"), + XBD: []byte("XBD"), + XCD: []byte("XCD"), + XDR: []byte("XDR"), + XOF: []byte("XOF"), + XPD: []byte("XPD"), + XPF: []byte("XPF"), + XPT: []byte("XPT"), + XSU: []byte("XSU"), + XTS: []byte("XTS"), + XUA: []byte("XUA"), + XXX: []byte("XXX"), + YER: []byte("YER"), + ZAR: []byte("ZAR"), + ZMW: []byte("ZMW"), + ZWL: []byte("ZWL"), +} + +var vals_inv_Currency = map[string]Currency{ + "": Undefined, + "AED": AED, + "AFN": AFN, + "ALL": ALL, + "AMD": AMD, + "ANG": ANG, + "AOA": AOA, + "ARS": ARS, + "AUD": AUD, + "AWG": AWG, + "AZN": AZN, + "BAM": BAM, + "BBD": BBD, + "BDT": BDT, + "BGN": BGN, + "BHD": BHD, + "BIF": BIF, + "BMD": BMD, + "BND": BND, + "BOB": BOB, + "BOV": BOV, + "BRL": BRL, + "BSD": BSD, + "BTN": BTN, + "BWP": BWP, + "BYN": BYN, + "BZD": BZD, + "CAD": CAD, + "CDF": CDF, + "CHE": CHE, + "CHF": CHF, + "CHW": CHW, + "CLF": CLF, + "CLP": CLP, + "CNY": CNY, + "COP": COP, + "COU": COU, + "CRC": CRC, + "CUP": CUP, + "CVE": CVE, + "CZK": CZK, + "DJF": DJF, + "DKK": DKK, + "DOP": DOP, + "DZD": DZD, + "EGP": EGP, + "ERN": ERN, + "ETB": ETB, + "EUR": EUR, + "FJD": FJD, + "FKP": FKP, + "GBP": GBP, + "GEL": GEL, + "GHS": GHS, + "GIP": GIP, + "GMD": GMD, + "GNF": GNF, + "GTQ": GTQ, + "GYD": GYD, + "HKD": HKD, + "HNL": HNL, + "HRD": HRD, + "HRK": HRK, + "HTG": HTG, + "HUF": HUF, + "IDR": IDR, + "ILS": ILS, + "INR": INR, + "IQD": IQD, + "IRR": IRR, + "ISK": ISK, + "JMD": JMD, + "JOD": JOD, + "JPY": JPY, + "KES": KES, + "KGS": KGS, + "KHR": KHR, + "KMF": KMF, + "KPW": KPW, + "KRW": KRW, + "KWD": KWD, + "KYD": KYD, + "KZT": KZT, + "LAK": LAK, + "LBP": LBP, + "LKR": LKR, + "LRD": LRD, + "LSL": LSL, + "LYD": LYD, + "MAD": MAD, + "MDL": MDL, + "MGA": MGA, + "MKD": MKD, + "MMK": MMK, + "MNT": MNT, + "MOP": MOP, + "MRU": MRU, + "MUR": MUR, + "MVR": MVR, + "MWK": MWK, + "MXN": MXN, + "MXV": MXV, + "MYR": MYR, + "MZN": MZN, + "NAD": NAD, + "NGN": NGN, + "NIO": NIO, + "NOK": NOK, + "NPR": NPR, + "NZD": NZD, + "OMR": OMR, + "PAB": PAB, + "PEN": PEN, + "PGK": PGK, + "PHP": PHP, + "PKR": PKR, + "PLN": PLN, + "PYG": PYG, + "QAR": QAR, + "RON": RON, + "RSD": RSD, + "RUB": RUB, + "RWF": RWF, + "SAR": SAR, + "SBD": SBD, + "SCR": SCR, + "SDG": SDG, + "SEK": SEK, + "SGD": SGD, + "SHP": SHP, + "SLE": SLE, + "SLL": SLL, + "SOS": SOS, + "SRD": SRD, + "SSP": SSP, + "STN": STN, + "SVC": SVC, + "SYP": SYP, + "SZL": SZL, + "THB": THB, + "TJS": TJS, + "TMT": TMT, + "TND": TND, + "TOP": TOP, + "TRY": TRY, + "TTD": TTD, + "TWD": TWD, + "TZS": TZS, + "UAH": UAH, + "UGX": UGX, + "USD": USD, + "USN": USN, + "UYI": UYI, + "UYU": UYU, + "UYW": UYW, + "UZS": UZS, + "VED": VED, + "VES": VES, + "VND": VND, + "VUV": VUV, + "WST": WST, + "XAF": XAF, + "XAG": XAG, + "XAU": XAU, + "XBA": XBA, + "XBB": XBB, + "XBC": XBC, + "XBD": XBD, + "XCD": XCD, + "XDR": XDR, + "XOF": XOF, + "XPD": XPD, + "XPF": XPF, + "XPT": XPT, + "XSU": XSU, + "XTS": XTS, + "XUA": XUA, + "XXX": XXX, + "YER": YER, + "ZAR": ZAR, + "ZMW": ZMW, + "ZWL": ZWL, +} + +func (s *Currency) UnmarshalText(text []byte) error { + var ok bool + if *s, ok = vals_inv_Currency[string(text)]; !ok { + return ErrUnknownCurrency + } + return nil +} + +func (s Currency) MarshalText() ([]byte, error) { return vals_Currency[s], nil } diff --git a/currency_enum_encoding_test.go b/currency_enum_encoding_test.go new file mode 100644 index 0000000..3a30a13 --- /dev/null +++ b/currency_enum_encoding_test.go @@ -0,0 +1,74 @@ +// Code generated by go-enum-encoding DO NOT EDIT +package fpmoney + +import ( + "encoding/json" + "errors" + "slices" + "testing" +) + +func TestJSON_Currency(t *testing.T) { + type V struct { + Values []Currency `json:"values"` + } + + values := []Currency{Undefined, AED, AFN, ALL, AMD, ANG, AOA, ARS, AUD, AWG, AZN, BAM, BBD, BDT, BGN, BHD, BIF, BMD, BND, BOB, BOV, BRL, BSD, BTN, BWP, BYN, BZD, CAD, CDF, CHE, CHF, CHW, CLF, CLP, CNY, COP, COU, CRC, CUP, CVE, CZK, DJF, DKK, DOP, DZD, EGP, ERN, ETB, EUR, FJD, FKP, GBP, GEL, GHS, GIP, GMD, GNF, GTQ, GYD, HKD, HNL, HRD, HRK, HTG, HUF, IDR, ILS, INR, IQD, IRR, ISK, JMD, JOD, JPY, KES, KGS, KHR, KMF, KPW, KRW, KWD, KYD, KZT, LAK, LBP, LKR, LRD, LSL, LYD, MAD, MDL, MGA, MKD, MMK, MNT, MOP, MRU, MUR, MVR, MWK, MXN, MXV, MYR, MZN, NAD, NGN, NIO, NOK, NPR, NZD, OMR, PAB, PEN, PGK, PHP, PKR, PLN, PYG, QAR, RON, RSD, RUB, RWF, SAR, SBD, SCR, SDG, SEK, SGD, SHP, SLE, SLL, SOS, SRD, SSP, STN, SVC, SYP, SZL, THB, TJS, TMT, TND, TOP, TRY, TTD, TWD, TZS, UAH, UGX, USD, USN, UYI, UYU, UYW, UZS, VED, VES, VND, VUV, WST, XAF, XAG, XAU, XBA, XBB, XBC, XBD, XCD, XDR, XOF, XPD, XPF, XPT, XSU, XTS, XUA, XXX, YER, ZAR, ZMW, ZWL} + + var v V + s := `{"values":["","AED","AFN","ALL","AMD","ANG","AOA","ARS","AUD","AWG","AZN","BAM","BBD","BDT","BGN","BHD","BIF","BMD","BND","BOB","BOV","BRL","BSD","BTN","BWP","BYN","BZD","CAD","CDF","CHE","CHF","CHW","CLF","CLP","CNY","COP","COU","CRC","CUP","CVE","CZK","DJF","DKK","DOP","DZD","EGP","ERN","ETB","EUR","FJD","FKP","GBP","GEL","GHS","GIP","GMD","GNF","GTQ","GYD","HKD","HNL","HRD","HRK","HTG","HUF","IDR","ILS","INR","IQD","IRR","ISK","JMD","JOD","JPY","KES","KGS","KHR","KMF","KPW","KRW","KWD","KYD","KZT","LAK","LBP","LKR","LRD","LSL","LYD","MAD","MDL","MGA","MKD","MMK","MNT","MOP","MRU","MUR","MVR","MWK","MXN","MXV","MYR","MZN","NAD","NGN","NIO","NOK","NPR","NZD","OMR","PAB","PEN","PGK","PHP","PKR","PLN","PYG","QAR","RON","RSD","RUB","RWF","SAR","SBD","SCR","SDG","SEK","SGD","SHP","SLE","SLL","SOS","SRD","SSP","STN","SVC","SYP","SZL","THB","TJS","TMT","TND","TOP","TRY","TTD","TWD","TZS","UAH","UGX","USD","USN","UYI","UYU","UYW","UZS","VED","VES","VND","VUV","WST","XAF","XAG","XAU","XBA","XBB","XBC","XBD","XCD","XDR","XOF","XPD","XPF","XPT","XSU","XTS","XUA","XXX","YER","ZAR","ZMW","ZWL"]}` + json.Unmarshal([]byte(s), &v) + + if len(v.Values) != len(values) { + t.Errorf("cannot decode: %d", len(v.Values)) + } + if !slices.Equal(v.Values, values) { + t.Errorf("wrong decoded: %v", v.Values) + } + + b, err := json.Marshal(v) + if err != nil { + t.Fatalf("cannot encode: %s", err) + } + if string(b) != s { + t.Errorf("wrong encoded: %s != %s", string(b), s) + } + + t.Run("when unknown value, then error", func(t *testing.T) { + s := `{"values":["something"]}` + var v V + err := json.Unmarshal([]byte(s), &v) + if err == nil { + t.Errorf("must be error") + } + if !errors.Is(err, ErrUnknownCurrency) { + t.Errorf("wrong error: %s", err) + } + }) +} + +func BenchmarkMarshalText_Currency(b *testing.B) { + var v []byte + var err error + for i := 0; i < b.N; i++ { + for _, c := range []Currency{Undefined, AED, AFN, ALL, AMD, ANG, AOA, ARS, AUD, AWG, AZN, BAM, BBD, BDT, BGN, BHD, BIF, BMD, BND, BOB, BOV, BRL, BSD, BTN, BWP, BYN, BZD, CAD, CDF, CHE, CHF, CHW, CLF, CLP, CNY, COP, COU, CRC, CUP, CVE, CZK, DJF, DKK, DOP, DZD, EGP, ERN, ETB, EUR, FJD, FKP, GBP, GEL, GHS, GIP, GMD, GNF, GTQ, GYD, HKD, HNL, HRD, HRK, HTG, HUF, IDR, ILS, INR, IQD, IRR, ISK, JMD, JOD, JPY, KES, KGS, KHR, KMF, KPW, KRW, KWD, KYD, KZT, LAK, LBP, LKR, LRD, LSL, LYD, MAD, MDL, MGA, MKD, MMK, MNT, MOP, MRU, MUR, MVR, MWK, MXN, MXV, MYR, MZN, NAD, NGN, NIO, NOK, NPR, NZD, OMR, PAB, PEN, PGK, PHP, PKR, PLN, PYG, QAR, RON, RSD, RUB, RWF, SAR, SBD, SCR, SDG, SEK, SGD, SHP, SLE, SLL, SOS, SRD, SSP, STN, SVC, SYP, SZL, THB, TJS, TMT, TND, TOP, TRY, TTD, TWD, TZS, UAH, UGX, USD, USN, UYI, UYU, UYW, UZS, VED, VES, VND, VUV, WST, XAF, XAG, XAU, XBA, XBB, XBC, XBD, XCD, XDR, XOF, XPD, XPF, XPT, XSU, XTS, XUA, XXX, YER, ZAR, ZMW, ZWL} { + if v, err = c.MarshalText(); err != nil { + b.Fatal("empty") + } + } + } + if len(v) > 1000 { + b.Fatal("noop") + } +} + +func BenchmarkUnmarshalText_Currency(b *testing.B) { + var x Currency + for i := 0; i < b.N; i++ { + for _, c := range []string{"", "AED", "AFN", "ALL", "AMD", "ANG", "AOA", "ARS", "AUD", "AWG", "AZN", "BAM", "BBD", "BDT", "BGN", "BHD", "BIF", "BMD", "BND", "BOB", "BOV", "BRL", "BSD", "BTN", "BWP", "BYN", "BZD", "CAD", "CDF", "CHE", "CHF", "CHW", "CLF", "CLP", "CNY", "COP", "COU", "CRC", "CUP", "CVE", "CZK", "DJF", "DKK", "DOP", "DZD", "EGP", "ERN", "ETB", "EUR", "FJD", "FKP", "GBP", "GEL", "GHS", "GIP", "GMD", "GNF", "GTQ", "GYD", "HKD", "HNL", "HRD", "HRK", "HTG", "HUF", "IDR", "ILS", "INR", "IQD", "IRR", "ISK", "JMD", "JOD", "JPY", "KES", "KGS", "KHR", "KMF", "KPW", "KRW", "KWD", "KYD", "KZT", "LAK", "LBP", "LKR", "LRD", "LSL", "LYD", "MAD", "MDL", "MGA", "MKD", "MMK", "MNT", "MOP", "MRU", "MUR", "MVR", "MWK", "MXN", "MXV", "MYR", "MZN", "NAD", "NGN", "NIO", "NOK", "NPR", "NZD", "OMR", "PAB", "PEN", "PGK", "PHP", "PKR", "PLN", "PYG", "QAR", "RON", "RSD", "RUB", "RWF", "SAR", "SBD", "SCR", "SDG", "SEK", "SGD", "SHP", "SLE", "SLL", "SOS", "SRD", "SSP", "STN", "SVC", "SYP", "SZL", "THB", "TJS", "TMT", "TND", "TOP", "TRY", "TTD", "TWD", "TZS", "UAH", "UGX", "USD", "USN", "UYI", "UYU", "UYW", "UZS", "VED", "VES", "VND", "VUV", "WST", "XAF", "XAG", "XAU", "XBA", "XBB", "XBC", "XBD", "XCD", "XDR", "XOF", "XPD", "XPF", "XPT", "XSU", "XTS", "XUA", "XXX", "YER", "ZAR", "ZMW", "ZWL"} { + if err := x.UnmarshalText([]byte(c)); err != nil { + b.Fatal("cannot decode") + } + } + } +} diff --git a/currency_test.go b/currency_test.go deleted file mode 100644 index 7f3dd96..0000000 --- a/currency_test.go +++ /dev/null @@ -1,80 +0,0 @@ -package fpmoney - -import ( - "fmt" - "testing" -) - -const numCurrencies = 181 - -func TestCurrency(t *testing.T) { - if len(currencies) != numCurrencies { - t.Errorf("wrong number of currencies(%d) exp(%d)", len(currencies), numCurrencies) - } - if len(fromAlpha) != numCurrencies { - t.Errorf("wrong number of currencies(%d) exp(%d)", len(fromAlpha), numCurrencies) - } -} - -func TestCurrencyBasic(t *testing.T) { - c := SGD - if c.String() != "SGD" { - t.Error("wrong stringer") - } -} - -func TestCurrencyTextEncoding(t *testing.T) { - tests := []struct { - c Currency - s []byte - }{ - { - SGD, - []byte("SGD"), - }, - } - for _, tc := range tests { - t.Run(fmt.Sprintf("%#v <-> %s", tc.c, tc.s), func(t *testing.T) { - var v Currency - if err := v.UnmarshalText(tc.s); err != nil { - t.Error(err) - } - if v != tc.c { - t.Error("wrong decode") - } - b, err := tc.c.MarshalText() - if err != nil { - t.Error(err) - } - if string(tc.s) != string(b) { - t.Error(b, "!=", tc.s) - } - }) - } -} - -func TestCurrencyTextEncoding_Error(t *testing.T) { - tests := []struct { - s []byte - }{ - {s: []byte("asdf")}, - {s: []byte("sgd")}, - {s: []byte(" SGD")}, - {s: []byte("SGD ")}, - {s: []byte("")}, - {s: []byte("\n")}, - {s: nil}, - } - for _, tc := range tests { - t.Run(fmt.Sprintf("%+v", tc.s), func(t *testing.T) { - var v Currency - err := v.UnmarshalText(tc.s) - if err == nil { - t.Error("expected error") - } - if (v != Currency{}) { - t.Error("wrong zero currency") - } - }) - } -} diff --git a/error.go b/error.go deleted file mode 100644 index 0a456da..0000000 --- a/error.go +++ /dev/null @@ -1,15 +0,0 @@ -package fpmoney - -type ErrCurrencyMismatch struct { - A, B Currency -} - -func NewErrCurrencyMismatch() *ErrCurrencyMismatch { return &ErrCurrencyMismatch{} } - -func (e *ErrCurrencyMismatch) Error() string { return e.A.Alpha() + " != " + e.B.Alpha() } - -type ErrWrongCurrencyString struct{} - -func NewErrWrongCurrencyString() *ErrWrongCurrencyString { return &ErrWrongCurrencyString{} } - -func (e *ErrWrongCurrencyString) Error() string { return "wrong currency string" } From c128b70865f94ab0a804d2f94853c3b871a7f042 Mon Sep 17 00:00:00 2001 From: Nikolay Dubina Date: Sat, 6 Apr 2024 15:34:15 +0800 Subject: [PATCH 2/3] nit --- .github/workflows/tests.yml | 53 ++++++++++++++++++++----------------- README.md | 35 +++++++++--------------- 2 files changed, 40 insertions(+), 48 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 371df1c..582af8b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -2,37 +2,40 @@ name: Test on: push: - branches: [ master ] + branches: [master] pull_request: - branches: [ master ] + branches: [master] permissions: read-all jobs: - build: name: Build runs-on: ubuntu-latest steps: - - - name: Set up Go 1.x - uses: actions/setup-go@v2 - with: - go-version: ^1.18 - id: go - - - name: Check out code into the Go module directory - uses: actions/checkout@v2 - - - name: Get dependencies - run: go get -v -t -d ./... - - - name: Test - run: go test -v -coverprofile=coverage.txt -covermode=atomic ./... - - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v2 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: coverage.txt - verbose: true + - name: Set up Go 1.x + uses: actions/setup-go@v2 + with: + go-version: ^1.18 + id: go + + - name: Check out code into the Go module directory + uses: actions/checkout@v2 + + - name: Get dependencies + run: go get -v -t -d ./... + + - name: Test + run: go test -v -coverprofile=coverage.txt -covermode=atomic ./... + + - name: Fuzz + run: | + go test -fuzz=FuzzArithmetics . + go test -fuzz=FuzzJSON_MarshalUnmarshal . + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v2 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: coverage.txt + verbose: true diff --git a/README.md b/README.md index ac2b32e..f3cf0e5 100644 --- a/README.md +++ b/README.md @@ -13,12 +13,11 @@ > _**Be Precise:** using floats to represent currency is almost criminal. — Robert.C.Martin, "Clean Code" p.301_ * as fast as `int64` -* no `float` in parsing nor printing +* no `float` in parsing nor printing, does not leak precision * `ISO 4217` currency * block mismatched currency arithmetics -* does not leak precision -* parsing faster than `int`, `float`, `string` * 100 LOC +* fuzz tests ```go var BuySP500Price = fpmoney.Amount{Amount: fpdecimal.FromInt(9000), Currency: fpmoney.SGD} @@ -42,22 +41,6 @@ json.NewEncoder(os.Stdout).Encode(amountToBuy) // Output: {"amount":18000.04,"currency":"SGD"} ``` -### Division - -Division always returns remainder. -Fractional cents can never be reached. - -```go -x := fpmoney.Amount{Amount: fpdecimal.FromInt(1), Currency: fpmoney.SGD} -a, r := x.Div(3) -enc := json.NewEncoder(os.Stdout) -enc.Encode(a) -enc.Encode(r) -// Output: -// {"amount":0.3333,"currency":"SGD"} -// {"amount":0.0001,"currency":"SGD"} -``` - ### Ultra Small Fractions Some denominations have very low fractions. @@ -66,10 +49,6 @@ Storing them `int64` you would get. - `BTC` _satoshi_ is `1 BTC = 100,000,000 satoshi`, which is still enough for ~`92,233,720,368 BTC`. - `ETH` _wei_ is `1 ETH = 1,000,000,000,000,000,000 wei`, which is ~`9 ETH`. If you deal with _wei_, you may consider `bigint` or multiple `int64`. In fact, official Ethereum code is in Go and it is using bigint ([code](https://github.com/ethereum/go-ethereum/blob/master/params/denomination.go)). -Given that currency enumn still takes at least 1B in separate storage from `int64` in struct and Go allocates 16B of memory for struct regardless, current implementation reserved padding bytes. -It is sensible to use extra space our ot 16B to support long integer arithmetics. -Implementing this is area of furthter research. - ### Benchmarks ```bash @@ -96,6 +75,16 @@ JSONMarshal/small-16 2.00 ± 0% 2.00 ± 0% 3.00 ± 0% JSONMarshal/large-16 2.00 ± 0% 2.00 ± 0% 3.00 ± 0% ``` +```bash +goos: darwin +goarch: arm64 +pkg: github.com/nikolaydubina/fpmoney +BenchmarkArithmetic/add_x1-16 1000000000 0.41 ns/op 0 B/op 0 allocs/op +BenchmarkArithmetic/add_x100-16 30528880 40.13 ns/op 0 B/op 0 allocs/op +PASS +ok github.com/nikolaydubina/fpmoney 14.200s +``` + ## References and Related Work - [ferdypruis/iso4217](https://github.com/ferdypruis/iso4217) was a good inspiration and reference material. it was used in early version as well. it is well maintained and fast library for currencies. From 4bd653eae5285eebbb4fb27be29e34c45118c9fd Mon Sep 17 00:00:00 2001 From: Nikolay Dubina Date: Sat, 6 Apr 2024 15:38:29 +0800 Subject: [PATCH 3/3] nit --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 582af8b..756ccb2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -30,8 +30,8 @@ jobs: - name: Fuzz run: | - go test -fuzz=FuzzArithmetics . - go test -fuzz=FuzzJSON_MarshalUnmarshal . + go test -fuzztime=5s -fuzz=FuzzArithmetics . + go test -fuzztime=5s -fuzz=FuzzJSON_MarshalUnmarshal . - name: Upload coverage to Codecov uses: codecov/codecov-action@v2