Dumb question, but what is the proper way to handle currency? Custom number objects? Strings for any number of decimal places?
Dumb question, but what is the proper way to handle currency? Custom number objects? Strings for any number of decimal places?
[Modern] programming languages have decimal/rational data types, which (within limits) are exact. Where this is not possible, and/or it's undesirable for any reason, just use an int and scale it manually (e.g. 1.05 dollars = int 105).
However, point 2 is very problematic and important to consider. How do account 3 items that cost 1/3$ each (e.g. if in a bundle)? What if they're sold separately? This really depends on the requirements.
My 20 cents: if you start a project, start storing currency in an exact form. Once a project grows, correcting the FP error problem is a big PITA (assuming it's realistically possible).
This caveat is kind of funny, in light of COBOL having support for decimal / fixed precision data types baked directly into the language.
It's not a problem with "non-modern" languages, it's a problem with C and many of its successors. That's precisely why many "non-modern" languages have stuck around so long.
https://medium.com/the-technical-archaeologist/is-cobol-hold...
Additionally, mainframes are so strongly optimized for hardware-accelerated fixed point decimal computing that for a lot of financial calculations it can be legitimately difficult to match their performance with standard commercial hardware.
Not really. Any semi-decent modern language allows the creation of custom types which support the desired behavior and often some syntactic sugar (like operator overloading) to make their usage more natural. Take C++, for example, the archetypal "C successor": It's almost trivial to define a class which stores a fixed-precision number and overload the +, -, *, etc. operators to make it as convenient as a built-in type, and put it in library. In my book, this is vastly superior to making such a type a built-in, because you can never satisfy everyone's requirements.
Okay, no idea how that's relevant to "built-in decimal types" vs "library-defined decimal types", but if it makes you feel better, you can do the same in Rust or Python, two languages which are "modern" compared to COBOL, don't inherit C's flaws, and which enable defining custom number types/classes/whatever together with convenient operator overloading.
Again, how is that relevant? If there's no way to enforce an invariant in custom data types, then there's also no way to enforce invariants in code using built-in data types.
Rust provides the mechanisms to enforce them, while in Python, like all dynamic languages, everything is up for grabs.
[1] (but was to lazy to write out)
You never account for fractional discrete items, it makes no sense. A bundle is one product, and a split bundle is another. For products sold by weight or volume, it's usually handled with a unit price, and a fractional quantity. That way the continuous values can be rounded but money that is accounted for needs not be.
My last job they wanted me to invoice them hours worked, which was some number like 7.6.
This number plays badly when you run it through GST and other things - you get repeaters.
So I looked up common practice here, even tried asking finance who just said "be exact", and eventually settled on that below 1 cent fractions I would round up to the nearest cent in my favour for each line item.
First invoice I hand them, they manually tally up all the line items and hours, and complain it's over by 55 cents.
So I change it to give rounded line items but straight multiplied to the total - and they complain it doesn't match.
Finally I just print decimal exact numbers (which are occasionally huge) and they stop complaining - because excel is now happy the sums match when they keep second guessing my invoices.
All of this of course was irrelevant - I still had to put hours into their payroll system as well (which they checked against) and my contract specifically stated what my day rate was to be in lieu of notice.
So how should you do currency? Probably in whatever form that matches how finance are using excel, which does it wrong.
BECAUSE EXCEL SUCKS MY DUDE.
I guarantee nothing in anyone’s time accounting system is measured to double-precision accuracy. Or at least, I’ve never quite figured out the knack myself for stopping work within a particular 6 picosecond window.
What's notable is clearly no one had actually thought this through at a policy level - the answer was "excel goes brrrr" depending on how they want to add up and subtotal things.
My most obnoxious and spicy programming take is that ints an decimals should be built-in and floats should require imports. I understand why though: Decimal encoding isn't anywhere near as standardized as other numeric types like integers or floating-point numbers.
[1] https://docs.python.org/3/library/decimal.html [2] https://docs.python.org/3/library/json.html
I don't care about making inexact numbers require imports, but the most natural literal formats should produce exact integers, decimals, and/or rationals.
https://money.howstuffworks.com/personal-finance/financial-p...
Googler, opinions are my own. Over in payments, we use micros regularly, as documented here: https://developers.google.com/standard-payments/reference/gl...
GCP on there other hand has standardized on unit + nano. They use this for money and time. So unit would 1 second or 1 dollar, then the nano field allows more precision. You can see an example here with the unitPrice field: https://cloud.google.com/billing/v1/how-tos/catalog-api#gett...
Copy/paste the GCP doc portion that is relevant here:
> [UNITS] is the whole units of the amount. For example if currencyCode is "USD", then 1 unit is one US dollar.
> [NANOS] is the number of nano (10^-9) units of the amount. The value must be between -999,999,999 and +999,999,999 inclusive. If units is positive, nanos must be positive or zero. If units is zero, nanos can be positive, zero, or negative. If units is negative, nanos must be negative or zero. For example $-1.75 is represented as units=-1 and nanos=-750,000,000.
Or if your language has something specific built in, use that.
Unless your language is PostgreSQL's dialect of SQL, apparently. https://wiki.postgresql.org/wiki/Don%27t_Do_This#Don.27t_use...
Note that if you use cents in the US so that everything is an integer then as long as you do not have to deal with amounts that are outside the range [-$180 trillion, $180 trillion] you can also use double. Double can exactly represent all integer numbers of cents in that range.
This may be faster than int64 on some systems, especially on systems that do not provide int64 either in hardware or in the language runtime so you'd have to do it yourself.
The arbitrary precision decimal type should be the default answer for currency until it is shown that the requirements no and at no time in the future will ever require fractional units of the smallest denomination.
As an aside, this may be constrained by the systems that the data is persisted into too... the Buffett Overflow is a real thing ( https://news.ycombinator.com/item?id=27044044 ).
I’d just add that if you are building a price prediction model, floats are probably what you need.
Also I'm hard pressed to come up with a case where floats would work. Can you give an example?
The answer is the same as _any_ time you should use floats: where you don't care about answers being exact, either (1) because calculation speed is more important than exactness, or (2) because your inputs or computations involve uncertainty anyway, so it doesn't matter.
This is more likely to be the case in, say, physics than it is in finance, but it's not impossible in the latter. For example, if you are a hedge fund and some model computes "the true price of this financial instrument is 214.55", you certainly want to buy if it's being sold for 200, and certainly don't if it's being sold for 250, but if it's being sold for 214.54, the correct interpretation is that _you aren't sure_.
When people say "you should never use floats for currency", their error is in thinking that the only applications for currency are in accounting, billing, and so on. In those applications, one should indeed use a decimal type, because we do care about the rounding behavior exactly matching human customs.
And the downstream parts for trade confirmation ("Middle Office"), settlement and accounting ("Back Office") used fixed precision. Because they are fundamentally accounting, which involves adding things up and cross-checking totals.
These two parts have a very clear boundary, with strictly defined rounding rules when the floating point risk/trading values get turned into fixed point accounting values.
There's plenty of good advice in this subthread for how to represent currency inside your Money abstraction, but whatever you do, keep it hidden. If you pass around numbers as currency values you will be in for a world of pain as your application grows.
This is the better default, so I'd ditch the qualifier, personally. At the very least when it comes to the persistent storage of monetary amounts. People often start out thinking that they won't need arbitrary precision until that one little requirement trickles into the backlog...
Arbitrary precision rationals handles all the artithmetic you could reasonably want to do with monetary amounts and it lets you decide where to round at display time (or when generating a final invoice or whatever), so there's no information loss.
In python, for exact applications (not many kinds of modeling, where floats are probably right), decimal.Decimal is usually the right answer, but fractions.Fraction is sometimes more appropriate, and if you are using NumPy or tools dependent on it, using integers (representing decimals multiplied by the right power of 10 to get the minimum unit in the ones position) is probably better.
The models also use transcendental functions which cannot be accurately calculated with fixed point, rationals, integers etc.
In accounting there are specific rules that require decimal system, so one must be very careful with the floating point if it is used.
I mean, fixed point and a specific type for currency (which also should include the denomination, while we are at it) are not rocket science. Spreadsheets get that right, at least.
Rounding error doesn't matter on these types of financial applications. It's the less glamorous accounting work that has to bother with that.
They're not rocket science, but they're unnecessary, and would still be off anyway. Try and calculate compound interest with your fixed point numbers.
The usual is to use decimal numbers with fixed precision (the actual precision varies from one country to another), and I don't know of any modern exception. But as late as the 90's there were non-decimal monetary systems around the world, so if you are saving any historic data, you may need something more complex.
Disclaimer: I only work with currency in hobby projects.