Or just work in floats and round as the last step. Or do both selectively depending on which rounding error works in your favor, but don't tell anyone that's what you're doing.
The real problem is that nobody at the company seems to have looked at the parts of their software that everyone sees, so what else have they not looked at?
That's not as easy to get right as one might like. Let's say we have an amount in dollars, represented as an IEEE 754 double, and a tax rate, also represented as a double. Let's assume that the amount is always some integer multiple of 0.01, and the tax rate is always some integer multiple of 0.00001.
Let's say we want the tax in cents. A first try might be:
unsigned long tax_in_cents(double subtotal, double rate)
{
return (unsigned long)round(subtotal * rate * 100);
}
(Doing tax in cents because C/C++ doesn't seem to have a standard variant of round() that lets you say to round to the nearest 0.01. It only rounds to integers).That will sometimes fail. The problem is it is rounding at the wrong place. It's logically rounding to the nearest multiple of 0.01, which is too crude. The rounding has to be much farther to the right.
This will do the trick:
unsigned long tax_in_cents(double amt, double rate)
{
const unsigned long M = 100 * 100000;
return (unsigned long)round((round(amt * rate * M) / (M/100)));
}
That will work for all rates from 0 to 1 that are integer multiple of 0.00001, and all amounts from 0 to 10000 that are integer multiples of 0.01, in all IEEE rounding modes. I've verified this via brute force. I'm not sure how high the amount can go before it breaks down.I'm not at all sure that if I had come across that first try in real life I would have noticed that it is not adequate.
Unless you're storing half cents in their account and not showing it.
Which is perfectly legitimate by the way, because if you earn a fraction of a cent interest accumulated over a couple months that might actually be a cent and you have to pay it.
The solution might be to store everything as 1/100 of a cent, as an integer.
In JavaScript:
> p=2500; r=0.347;
< 0.347
> Math.round(p*r)
< 867
You need to integerize the percentage, too. For my current financial applications, all the percentages I need are integer multiple of 0.01%, so I use that as my "percent cent". 34.7% is then represented as 3470.That leads to a function something like this (assuming you need the 0.5 round up rule...changing it to 0.5 rounds to even is left as an exercise):
function percent(amt2, rate4)
{
return Math.floor((amt2 * rate4 + 5000)/10000)
}
(The 2 and 4 suffixes are a naming convention I use. They are reminders that amt2 is the underlying amount x 100, and that rate4 is the underlying rate x 10000).http://csharphelper.com/blog/2017/05/compare-the-performance...
I would rather recommend using an arbitrary precision decimal library like Decimal.js, and using strings to represent money in JSON. And of course using an appropriate database type for storing the data.
What you gain by consistently using an arbitrary precision decimal library to process all monetary values, is the freedom to process smaller fractions later if necessary. Also you'll never overflow with really big values.
In other words, when you represent monetary values with arbitrary precision Decimal objects everywhere, you can easily expand your application as much as you want on either side of the decimal dot. Changing existing integer-based code do to that is very error-prone, you quickly lose sight of what exactly an integer value or parameter means in different places.
Edit: And sum your (optionally price-weighted) time first.