4

I'm building an online store in Go. As would be expected, several important pieces need to record exact monetary amounts. I'm aware of the rounding problems associated with floats (i.e. 0.3 cannot be exactly represented, etc.).

The concept of currency seems easy to just represent as a string. However, I'm unsure of what type would be most appropriate to express the actual monetary amount in.

The key requirements would seem to be:

  • Can exactly express decimal numbers down to a specified number of decimal places, based on the currency (some currencies use more than 2 decimal places: http://www.londonfx.co.uk/ccylist.html )
  • Obviously basic arithmetic operations are needed - add/sub/mul/div.
  • Sane string conversion - which would essentially mean conversion to it's decimal equivalent. Also, internationalization would need to be at least possible, even if all of the logic for that isn't built in (1.000 in Europe vs 1,000 in the US).
  • Rounding, possibly using alternate rounding schemes like Banker's rounding.
  • Needs to have a simple and obvious way to correspond to a database value - MySQL in my case. (Might make the most sense to treat the value as a string at this level in order to ensure it's value is preserved exactly.)

I'm aware of math/big.Rat and it seems to solve a lot of these things, but for example it's string output won't work as-is, since it will output in the "a/b" form. I'm sure there is a solution for that too, but I'm wondering if there is some sort of existing best practice that I'm not aware of (couldn't easily find) for this sort of thing.

UPDATE: This package looks promising: https://code.google.com/p/godec/

Brad Peabody
  • 10,917
  • 9
  • 44
  • 63
  • Quick reaction: think you actually want a `struct` type that includes units, culture (since EUR amounts are printed different ways in different countries), and amount, possibly as a fixed-point int (e.g., for USD, cents or cents*10; stored precision could vary by country, or you could always use, say, 4 digits to control rounding error). If you want to control rounding, I think you have to gate math operations through methods (i.e., amt.Div(3) not amt.Units /= 3). – twotwotwo Sep 24 '14 at 04:15
  • Storing it its own interesting thing; you might want *three* database columns, the amount, the currency, and the amount roughly converted into the customer's standard currency at the time of transaction, wherever currencies were stored. – twotwotwo Sep 24 '14 at 04:17
  • I couldn't find an existing model quickly, but broadly I think you're going to want to implement something yourself and it's more [fire and motion](http://www.joelonsoftware.com/articles/fog0000000339.html) than clever. – twotwotwo Sep 24 '14 at 04:22
  • Oh, hey, can't vouch for it but check oot https://github.com/Confunctionist/finance – twotwotwo Sep 24 '14 at 04:25
  • @twotwotwo Thanks for the info - yeah I'm thinking I'll have to roll my own; and yes making a struct that encapsulates currency and amount (likely with big.Rat) does makes sense. In my case I'm keeping track of locale (language+country/region) separately at the UI layer, since currency and amount effect the actual "price" intended, whereas the locale is really just a display thing and determines how you format it for the particular user looking at it. – Brad Peabody Sep 24 '14 at 04:41

1 Answers1

5

You should keep i18n decoupled from your currency implementation. So no, don't bundle everything in a struct and call it a day. Mark what currency the amount represents but nothing more. Let i18n take care of formatting, stringifying, prefixing, etc.

Use an arbitrary precision numerical type like math/big.Rat. If that is not an option (because of serialization limitations or other barriers), then use the biggest fixed-size integer type you can use to represent the amount of atomic money in whatever currency you are representing – cents for USD, yens for JPY, rappen for CHF, cents for EUR, and so forth.

When using the second approach take extra care to not incur in overflows and define a clear and meaningful rounding behaviour for division.

thwd
  • 23,956
  • 8
  • 74
  • 108