For that tax rate, or in general tax rates that range from 0.001% to 99.999% in increments of 0.001%, and amounts in dollars to 2 decimal places, you
can safely calculate the tax in cents using floating point,
but it is not necessarily obvious how to do it so unless you
must do it in floating point, I'd not.
In C:
unsigned long tax_in_cents(double amt, double rate)
{
/* requirement: amt is an integer multiple of 0.01
and rate is an integer multiple of 0.00001 */
const unsigned long M = 10000000;
return (unsigned long)round((round(amt * rate * M) / (M/100)));
}
That should work, at least for amt that isn't too high.
Here is how I would do it, using integers:
unsigned long tax_in_cents(unsigned long amt2, unsigned long rate5)
{
/* amt2 is the amount in cents, rate5 is tax rate * 10000 */
return (amt2 * rate5 + 50000) / 100000;
}
(My convention in this kind of code is that if an underlying thing is represented as an integer by multiplying it by a power of 10, I put that power as a suffix on the integer variable name. So amt2 means the real amount x 100, and rate5 means the tax rate x 100000. A ...2 x ...5 gives something that is ...7, which is why we divide by 10^5 to get the tax in cents (a ...2). Half of the divisor, or 10^5/2, is what you add for rounding).
I've not proven the above floating point code, but I have brute force tested it with every combination of rates from 0.00001 to 0.99999, price from $0.01 to $10000.00, and floating rounding modes FE_UPWARD, FE_TONEAREST, and FE_DOWNWARD.
Most initial attempts to write such a function make the mistake of doing the rounding at the wrong place. They just try something like amt x rate then round that to the nearest cent.
The problem with that can be seen by a very simple example: 10% of $21.15. It should be $2.12. It comes out before rounding as 2.1149999999999998, so rounding to the nearest 0.01 comes out as $2.11. Various "obvious" ways you might do this might work for that case, but they fail in others. For example, here are three ways in Python that readily come to mind:
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)
but try all of those on these:
1% of $21.50
3% of $21.50
6% of $21.50
10% of $21.15
and every one of them fails at least once. The correct answers 22, 65, 129, and 212. Here is what they give, with the wrong ones prefixed with !:
tax_f1: !21 65 129 !211
tax_f2: 22 !64 129 !211
tax_f3: 22 65 !130 212
There was a discussion of this here a couple months ago [1]. In that discussion, someone who knows considerably more about floating point that I do (kccqzy) suggested how to fix it, and even his first try failed when I run it through my brute force testing. He then suggested a method that worked (at least according to my brute force testing).
[1] https://news.ycombinator.com/item?id=21688373