rhymond / go-money Goto Github PK
View Code? Open in Web Editor NEWGo implementation of Fowler's Money pattern
License: MIT License
Go implementation of Fowler's Money pattern
License: MIT License
Say I want to do an allocation of 1.95,98.05
. How would I do that with the current Allocate
method? As far as I can tell, it only supports whole number percentages. I can easily do 2,98
.
It would be nice if the Money
struct has methods to add or subtract multiple values. For example:
func( *m Money) AddMultiple(om... *Money) (*Money, error){
}
func( *m Money) SubtractMultiple(om... *Money) (*Money, error){
}
I write a lot of test cases that calculates money and invoices and I often need to add up multiple values in a test case to check if the calculated amount is correct. It's possible to do this using the Add()
method, but it's quite tedious to have to check the error for each addition. I am currently using a helper function to do this, but I think this would be a very useful addition to the library.
I'd like to do the implementations of scanner and valuer for Money
:
Consider this:
Money {
Amount: 10,
Currency: EUR,
}
I see several ways to represent it in database.
// Scanner and Valuer implemented on `money.Money`.
type someModel struct {
Money money.Money, `db:"money"`
}
INSERT INTO foo (money) VALUE ('EUR 10');
A string field in database holding both the amount and the currency.
Represent two distinct fields in database, one for the amount and the other for the currency.
money.Amount
and money.Currency
.// Scanner and Valuer implemented on `money.Amount` and `money.Currency`.
type someModel struct {
Amount money.Amount, `db:"amount"`
Currency money.Currency `db:"currency"`
}
INSERT INTO foo (amount, currency) VALUE (10, 'EUR');
I prefer solution B
, what do you think @Rhymond ?
Packages should return errors so that the consumer of the package can choose what happens.
Packages should not log things, and they should not exit the application via log.Fatal
Panics in packages are somewhat acceptable if some startup dependency is incorrect and the package cannot work without it.
Fixing this will require a change in the API, and thus should require a version increment.
package main
import "github.com/Rhymond/go-money"
func main() {
pound := money.New(100, money.GBP)
twoPounds, err := pound.Add(pound)
if err != nil {
log.Fatal(err)
}
parties, err := twoPounds.Split(3)
if err != nil {
log.Fatal(err)
}
parties[0].Display() // £0.67
parties[1].Display() // £0.67
parties[2].Display() // £0.66
}
Could be updated to
package main
import (
"fmt"
"log"
"github.com/Rhymond/go-money"
)
func main() {
pound := money.New(100, "GBP")
twoPounds, err := pound.Add(pound)
if err != nil {
log.Fatal(err)
}
parties, err := twoPounds.Split(3)
if err != nil {
log.Fatal(err)
}
for _, v := range parties {
fmt.Println(v.Display())
}
}
Why is the float64 value equal to 1.15 created in the money type as 1.14?
The link below shows an example of the supposed error:
https://go.dev/play/p/Z64XrP8LdTc
It would be really useful to be able to create a new Money struct from a float or maybe a decimal string ('1.243'). Formatting the decimal amount from a source (API response, file, ...) to a Money struct can get really tedious, that's why I propose a util function inside the money package for dealing with this cases.
This is my current implementation:
func NewFromFloat(amount float64, currency string) *money.Money {
currencyDecimals := math.Pow(10, float64(money.GetCurrency(currency).Fraction))
amountCents := int64(amount * currencyDecimals)
return money.New(amountCents, currency)
}
To not lose precision dealing with floats there can also be a NewFromString, but I haven gotten around to implementing it.
What do you guys think?
See: https://github.com/Rhymond/go-money/blob/master/calculator.go#L52
Instead of using 100
is should be something like int64(math.Pow(10, c.Exponent))
or simply a lookup table. Same for 50
.
And the readme should state it only supports decimal currencies (i.e. decimals with cents); there are other currencies of base 5 for example.
I tried to run the first example in the Readme.md text.
Two issues immediately show up:
Would s/o please explain to me why Amount
is a struct and not just a type alias which I expected to be more simple and sufficient?
// Amount is a datastructure that stores the amount being used for calculations.
type Amount struct {
val int64
}
It seems more odd when I saw when the Amount is fetched we are getting the value.
func (m *Money) Amount() int64 {
return m.amount.val
}
I know that most majority of currencies have 100 minor units in one major or don't have a minor at all and it's okay to save their fraction (2, 0 or 3) and use them for converting:
func (f *Formatter) ToMajorUnits(amount int64) float64 {
if f.Fraction == 0 {
return float64(amount)
}
return float64(amount) / float64(math.Pow10(f.Fraction))
}
But my suggestion is to be more pragmatic and remember about non-decimal currency and etc:
And if we use the number of minor units we can remove this math.Pow10
function and the result will look like:
func (f *Formatter) ToMajorUnits(amount int64) float64 {
if f.Units == 0 {
return float64(amount)
}
return float64(amount) / float64(f.Units)
}
When trying to allocate an amount to a big amount net, it results on a panic error.
How to reproduce:
Link: https://play.golang.org/p/lZaEXY0Jaqg
package main
import (
"github.com/Rhymond/go-money"
)
func main() {
net := money.New(2911127909321522, "EUR")
net.Allocate(30000)
}
When calling the Display() method for a given object i get the following output:
€1,000.00
But the correct german format is 1.000,00 €
(in most cases; sometimes it would be € 1.000,-
). This format varies even between different EURO countries. And then there is the problem with dot and comma as decimal marks.
That's why it would be nice to define a custom formatter before calling Display().
Creation of a go.mod file in compliance with go new way of handling dependencies.
@Rhymond what do you think of it ?
By making money.New()
accept an int64
rather than a float or more inline with other libraries do (possibly more idiomatic?) a *big.Float
it cuts off the ability to use a value with a decimal amount during allocation.
dollar := money.New(3.50, "USD") // type error
dollar := money.New(big.NewFloat(3.50), "USD") // would work
Right now there is a calculator
variable that is global and created anew every time a money instance is created.
This is an issue and will cause races. Multiple money objects being created and used on multiple goroutines will cause indeterminate results due to the races.
The calculator
instance should be inside the money
instance, and be created in the money
constructor.
It would be fantastic if this library supported the formatting and parsing of Money
as strings with the currency code instead of symbol (e.g. USD 1.00
) which eliminates any ambiguity caused by symbols being shared across currencies (e.g. $1.00
may refer to USD
, AUD
, CAD
, etc).
This also enables the library to be used when storing money amounts in a database as a string, as these can be parsed when being retrieved instead of just being formatted when inserted.
Formatter as Data Mutator should be moved to Mutator struct
Hi @Rhymond,
I was wondering how do you envision the future of go-money in terms of:
golang.org/x/text/currency
Thanks!
Am building a templating system and need to handle currency properly.
Does this library have functionality for this ?
E.g in Sweden and Germany money is displayed differently
If you know of I lib I can combine and PR if you want too
Using the architecture-independent int
type will cause 32-bit binaries to overflow if more than 21,474,836.47
is used in a base-100 currency, introducing architecture-dependent behaviour differences which may not be caught.
This can be resolved by switching amounts to use the int64
data type. This will still work on 32-bit architectures, at the slight expense of performance, and allow base-100 currency values up to an amount of 92,233,720,368,547,758.07
... More than likely enough for most consumers of this package :)
This is from https://en.wikipedia.org/wiki/ISO_4217
Also, all payment providers, I deal with they use fraction of 2 instead of 0
...int
is easier to use, and can also easily accept a []int
.
Hi everyone,
I'm actually trying to marshal / unmarshal the Money structure but, obviously because of the current implementation, it is not possible yet.
I saw that someone has already required a PR regarding that topic :
Will it be validated ? I think it is a really important feature 👍
Thanks by advance !
Thanks for a great package!
It would be quite convenient to have methods on Money
that panic instead of returning error. These could wrap the existing methods, with the Must
prefix. For example, MustAdd
would wrap Money.Add
.
In my program, for example, I deal with just one currency (and am using go-money for correct handling of cents in the dollar). The ability to avoid explicit error checking, and use the result of Add
, Subtract
, etc. as an unary expression would be very helpful.
HUF has an exponent of 2, not 0.
KPW has an exponent of 2, not 0.
See the ISO standard:
https://www.six-group.com/dam/download/financial-information/data-center/iso-currrency/lists/list_one.xml
Hi Rhymond,
thank you for creating this amazing tool!
I noticed that there is no support for the currency of Myanmar, may I create a PR and add it? thanks!
e,g)
Display amount of SDG
input 136.98
expected: $136.98
actual: $136.97
seeing is believing ↓
https://go.dev/play/p/l4imxLkT70u
Hi guys
I've found out that the library doesn't include ZWL.
https://github.com/Rhymond/go-money/blob/master/currency.go#L48
ZWL must replace ZWD according to ISO_4217: https://en.wikipedia.org/wiki/ISO_4217
Please fix it.
I found that a small negative amount will be split wrongly.
Example
package main
import (
"fmt"
"github.com/Rhymond/go-money"
)
func main() {
var val = money.New(-2, "EUR")
parts, err := val.Split(3)
if err != nil {
fmt.Println(err)
}
for _, part := range parts {
fmt.Println(part.Display())
}
}
Expected output
-€0.01
-€0.01
-€0.00
Actual output
€0.01
€0.01
€0.00
It seems that if a money value is less than the number of split parts it returns a wrong sign because 0.04/3 returns 0.02,0.01,0.01.
Hi,
When using INR currency format, we are not getting the desired result:
Result:
1000.00 -> ₹1,000.00 (As expected)
10000.00 -> ₹10,000.00 (As expected)
100000.00 -> ₹100,000.00 (Expected ₹1,00,000.00)
1000000.00 -> ₹1,000,000.00 (Expected ₹10,00,000.00)
and so on
Can you provide a solution for this since this is regarding the placement of separator not the value of separator?
Hi, would it be possible (and make sense of course) the money.New
function to return an error if the provided currency code is not in the currencies list (e.g., "XYZ") instead of just creating it with default values? Less important and debatable, would making the default configurable (as more of a fallback) an interesting feature (e.g., set default as USD for example)?
Love the new addition of NewFromFloat
. Just wanted to get a clearer understanding of always rounding trailing decimals down when given.
cents := money.NewFromFloat(99.999999, money.USD)
//-> cents: 9999
Just want to know the why between getting 9999 vs 10000?
Thanks!
There are several methods to compare two Money object but there is no Money.Compare(Money)
function.
The proposal is to add a Compare function for Money.
// Compare returns an integer comparing two Money objects
// If Money objects are of different currency, an ErrCurrencyMismatch is returned.
// If both objects are for same currency, their amounts are compared.
// The result will be 0 if a == b, -1 if a < b, and +1 if a > b.
func (m *Money) Compare(om *Money) int {
if err := m.assertSameCurrency(om); err != nil {
return false, err
}
return m.compare(om), nil
}
I'll provide a PR.
This is a great package.
Would you mind updating the documentation on how we can use different formats used around the world, like other European countries, not to mention India where they separate digit groups differently?
I would mainly like to get
1.244,90 €
instead of
€1,244.90
If i want to use this package in a application, I need to remember to copy the currencies.json file in a resource folder on the target machine. That's not very idiomatic.
Since the list isn't that big I think it would be better to just include it directly in the go codebase.
It would be helpful to retrieve the currency
and amount
information from a money
object.
Multiply
and Divide
only accept an int. What was the reason for this? It seems to lessen the usefulness of these functions. For example, calculating the net value from gross is impossible.
gross := money.New(599, "GBP")
net := gross.Divide(1.2) // error
tax, _ := gross.Subtract(net)
or am I missing something and this is possible in another way?
Like the net/http package status codes and the use of time.Second
etc. in the time package, I suggest providing currency codes as public constants.
I understand that they would represent a string of the same name but would make it easier to ensure that the currency code you're using is correct through autocompletion in editors.
I wanted to gather some feedback on this before having a go at implementing it. It shouldn't change the API as the constants will just act as helpers that can be used instead of passing a string literal as an argument, for example:
pound := money.New(100, money.GBP)
I've added a functionality that allows the user to get the value of the money in major units (float64).
for example:
money.New(123456789, "EUR").AsMajorUnits() // 1234567.89
Also, I've filled out some more currencies and fixed some that we're incorrect like COP
that supposed to have fraction of 2 but was set as 0.
Hello,
The Allocate function can panic in the if the given ratios are all zero.
This makes sense, since the function will try to divide de given amount in parts of zero.
However, in my application it can make sense to try to allocate zero money, which would be the sum of some item prices, using those same item prices as ratios. Since the items add up to zero, because they are free, you get a call like
amount := money.New(0, money.EUR)
parties, err := amount.Allocate(0,0,0)
I wonder if, in this case, the Allocate function shouldn't return a slice filled with zeros with the same length as the given ratios, instead of panicking while dividing by zero.
On the other hand, Allocating is just another word for dividing, and zero divided by zero should be undefined, and not zero.
Running go get github.com/rhymond/go-money
fails and spits out the message:
go: github.com/rhymond/[email protected]: parsing go.mod: unexpected module path "github.com/Rhymond/go-money"
go: error loading module requirements
seems to be related to golang/go#27154
Hello
I am a happy user of go-money and use it in a private webapp with database.
I had to make some adaptations to make go-money usefull most notably I had to add sql compatibility. As I did not want to make changes to go-money at that time I just wrapped go-money into another struct and copy data into and out of the go money struct on read/write to the database. Needless to say I am not a fan of that solution but it worked for my needs.
Now that I have some time on my hands, I would like to implement that Feature into go-money directly so that Life is easier for me.
What I did was implement Scanner and Valuer Interface and save the Data as semicolon delimited string in the Database. That was enough to save amount and currency information for later Object reconstruction during Read.
Would you like to keep that implementation or do you have another idea in what format to save the Data to database?
I could also make Inherited types so that the Main type does not force a certain usage over another.
what are your thoughts on this?
I just noticed that the readme has a flawed example:
The import isn't good, we have "github.com/rhymond/go-money"
instead of "github.com/Rhymond/go-money"
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.