Earlier quoted context omitted.
Storing money in floating point is always terrible. If speed is an issue, store it in integer types representing the smallest unit in the currency, e.g. pennies. Unless you’re doing, what, massively parallel GPU algos on batches of independent amounts? But even then you could use the float as an int in that way... Honestly when is float ever actually good for money? Not for speed, not for correctness, ...
Storing money in floating point is fine. Just round to the nearest atomic unit when displaying. Sometimes this is a necessity when working with money in e.g. existing JSON APIs. You lose a few bits of range relative to fixed point storage but it's almost never a practical issue. Performing arithmetic operations against money in floating point is the dangerous part, as error can accumulate beyond an atomic unit.
A good example of this is trying to compute the sales tax on $21.15 given a tax rate of 10%. The exact answer would be $2.115, which should round to $2.12.
IEEE 64-bit floating point gives 2.1149999999999998, which is hard to get to round to 2.12 without breaking a bunch of other cases.
Here are three functions that try to compute tax in cents given an amount and a rate, in ways that seem quite plausible:
def tax_f1(amt, rate):
tax = round(amt * rate,2)
return round(tax * 100)
def tax_f2(amt, rate):
return round(amt*rate*100)
def tax_f3(amt, rate):
return round(amt*rate*100+.5)
On these four problems: 1% of $21.50
3% of $21.50
6% of $21.50
10% of $21.15
the right answers are 22, 65, 129, and 212. Here are what those give: tax_f1: 21 65 129 211
tax_f2: 22 64 129 211
tax_f3: 22 65 130 212
Note that none of the get all four right.I did some exhaustive testing and determined that storing a money amount in floating point is fine. Just convert to integer cents for computation. Even though the floating point representation in dollars is not exact, it is always close enough that multiplying by 100 and rounding works.
Similar for tax rates. Storing in floating point is fine, but convert to an integer by multiplying by an appropriate power of 10 first. In all the jurisdictions I have to deal with, tax rate x 10000 will always be an integer so I use that.
Give amt and rate, where amt is the integer cents and rate is the underlying rate x 10000, this works to get the tax in cents:
def tax(amt, rate):
tax = (amt * rate + 5000)//10000
return tax
I'm not fully convinced that you cannot do all the calculations in floating point, but I am convinced that I can't figure it out.