4 ms·
How would you suggest to store and calculate things like taxes? For example in NYC the retail sales tax is 8.735%. Obviously the final amount could be stored as
by fyfy18 7y ago
How would you suggest to store and calculate things like taxes? For example in NYC the retail sales tax is 8.735%. Obviously the final amount could be stored as an integer in cents, but I'm talking about the tax rate and the calculations of it. I guess you need to calculate the tax for each item, round it to cents (and I assume the rules on how you round can vary by jurisdiction, so it's not just calling your language's round function), multiply that by the quantity of that item (good luck if the quantity can be a decimal value), then sum each line item to get the total tax.
- dragonwriter 7y ago> Obviously the final amount could be stored as an integer in cents, but I'm talking about the tax rate and the calculations of it. The tax rate (like most things associated with money) should be stored as an exact decimal (fixed point decimal, decimal floating point, or arbitrary precision decimal all work.) > I guess you need to calculate the tax for each item, round it to cents (and I assume the rules on how you round can vary by jurisdiction, so it's not just calling your language's round function), multiply that by the quantity of that item (good luck if the quantity can be a decimal value), then sum each line item. Usually taxes are required to be calculated by summing the pre-tax cost of all line items, applying the tax rate, rounding by the applicable rules, and then adding that to the pre-tax subtotal to get the final total. But having decimal unit prices (even decimal cents, e.g., gas is usually priced in mils, not cents), decimal quantities, and decimal tax rates are all non-problems (so no "good luck" needed) as long as you are using an exact decimal representation rather than binary floating point.
- jaclaz 7y agoTo be fair, there are a range of little issues when applying tax rates, not connected to the representation of the numbers involved. In Italy the generic VAT rate is 22%. When you expose a price (to the final customer) it is always included VAT, but VAT needs to be explicited on fiscal documents (receipt and/or invoices). So you have an article priced to the public Euro 2,69 (included 22% VAT), what is the net price and how much is the VAT? 2,69/1,22=2,204918... So, let's try rounding it to 2,20: 2,20x0,22=0,484=0,48 no, wait, 2,20+0,48=2,68 Ok, let's try rounding it to 2,21: 2,21x0,22=0,4862=0,49 no, wait, 2,21+0,49=2,70 You have to use the first and add 0,01 to the VAT to have it work: 2,20+(0,48+0,01)= 2,69
- Yaa101 7y agoEasy, either implement a decimal type (4 fractional digits) as most programming languages have them implemented by users. or multiply by 10000 then do your calculation and then divide by 10000 again. I used a decimal type for the administration program I coded.
- dragonwriter 7y ago> Easy, either implement a decimal type (4 fractional digits) as most programming languages have them implemented by users. C#, Ruby, Python, and Java, at least, among industrially popular languages all provide a fixed and/or arbitrary precision decimal type in either the core language or stdlib. Lower level languages sometimes don't, and JavaScript, among higher-level popular languages, notably does not.
- geofft 7y ago> Easy, either implement a decimal type Great, how do you become confident that your hand-rolled decimal type is less buggy than the known hazards of using floating-point?
- jdlshore 7y agoIf you’ve never done something like this, you might want to try it. It’s a nice simple programming exercise and the basic version, with tests, will take less than an evening.
- tzs 7y agoFor 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 https://news.ycombinator.com/item?id=21688373