I did join somewhere that could do it correctly, because they had some very long-running POS software. It could even do things like "split bill three ways" correctly allocating both the spare penny from the division and the tax calculation, such that you could add the bills back together again and get the same numbers as the split bill.
Using a "money" class that stores things as integer pennies gets you a long way there. "Division" and its close friend "multiply by noninteger number" are the only real problems, so you need to be careful not to provide a generic method and instead methods like divideWithLocaleTaxRounding(). You also need to check whether you're supposed to apply tax per-item or you can do it to the whole bill.
I think we had a "apply tax to whole bill but then re-distribute it to line items" method, which guaranteed that the total would be correct.
There are reasonable arguments for "integer decimal fraction of penny" as the correct unit. Digikey price some parts in 0.1 of a penny or cent, for example.
Attempting to convert things between binary "fractions" (floating point) and decimal fractions will result in misery.
I think we also had a "rational number" class for storing things like "1/3".
Integer cents is fantastic for POS software and similar things that deal with at most a few thousand dollars at a time. The place where it starts to fail is when the absolute numbers get really large, not really small. Think "United States Federal Reserve" or "UBS". Then remember that some of these institutions need to deal with accounts denominated in Zimbabwean dollars.
Really small can be an issue when it gets really small e.g. tarsnap’s accounting is pretty infamously done in attodollars (1e-12). U64 only has room for 19 decimal digits so you’re limited to 7 figures up.
Tarsnap's pricing is in picodollars (1e-12) but the accounting is attodollars (1e-18). Tarsnap uses 128-bit balances -- 64 bits of attodollars and 64 bits of whole dollars.
If I ever have someone paying me more than 2^64 dollars, I'll rewrite Tarsnap's accounting system.
Funny thing, I actually wrote one POS application (magstripe + EMV chip & pin + contactless).
EMV uses integers encoded as BCD for money. If I remember well, in most cases it is 6 bytes or 12 digits. That is more than 2^32 (most of POS machines were 32 bit ARM until relatively recently).
The terminal I worked on had 32 bit ARM. Rather than convert 12 digit numbers to 32 bit integers I decided to write my own arithmetic library that did operations directly on BCD strings of arbitrary length (but, in practice, EMV only allows 6 bytes for the amount anyway).
The current US national debt represented in integer cents requires 52 bits. It can trivially increase 4,000x before we need to worry about 64-bit balances.
Using a "money" class that stores things as integer pennies gets you a long way there. "Division" and its close friend "multiply by noninteger number" are the only real problems, so you need to be careful not to provide a generic method and instead methods like divideWithLocaleTaxRounding(). You also need to check whether you're supposed to apply tax per-item or you can do it to the whole bill.
I think we had a "apply tax to whole bill but then re-distribute it to line items" method, which guaranteed that the total would be correct.
There are reasonable arguments for "integer decimal fraction of penny" as the correct unit. Digikey price some parts in 0.1 of a penny or cent, for example.
Attempting to convert things between binary "fractions" (floating point) and decimal fractions will result in misery.
I think we also had a "rational number" class for storing things like "1/3".