Hacker News new | past | comments | ask | show | jobs | submit login

The article is about data types for storage, not for intermediary values used as part of calculations, though. Are you proposing that everybody is storing monetary values "wrong", too?

And as a meta-point:

> I have been fighting over this with countless people, teams and companies. [...] I have never in my life joined a software project for any organisation that was able to do basic arithmetic on money correctly [...]

Are you absolutely sure that you are the only person that understands how to do accounting arithmetics on computers correctly?

My guess would be that the status quo is a combination of a lot of legacy code and procedures, but more importantly of differing priorities.

Maybe you value arithmetic correctness over simplicity of procedures (sometimes these need to be published in regulatory texts or even laws) or compatibility with other entities and their procedures much more than the industry average?




A shocking number of people (edit: who implement billing related software) are unaware of how many decimal points of accuracy their local tax code requires to calculate vat or sales tax correctly. And those things are often specified in terms of arithmetic correctness.

I had our CFO stand behind me while I talked him through every step of our VAT calculations once, because he was legally responsible if I made us round it wrong, to the wrong number of digits. And had we e.g. done something grossly incompetent like used floats for those calculations it most certainly would have been wrong, but so would it if I used fewer than five digits past the decimal point or failed to round in the right direction after that.

It's usually not hard, but it requires being aware that you need to look up the right rules. And know better than using floats.


A shocking number of people who work with software that handles money are just winging it. I worked on a project once that handled payment processing functionality for other products at the company (it was a B2B SaaS where clients could take payments through our software). We also handled payments related billing, since the clients would owe fees to us and to our payments gateway for their transactions.

The payments gateway we worked with calculated their fees to a thousandth of a cent, but of course we could only bill whole cents. So basically every billing period there would be a < $0.01 balance due that carried over to the next bill, and every so often the carryover would add up to a full cent that needed to be charged. When we implemented our MVP we (engineering) explained this to the product and business teams, and it blew their minds. Our suggestion was to have a tooltip on the 1 cent charge with a link to a help article explaining how the accounting worked, but they were strongly against it and had us list the 1 cent as something like "other fees" with no explanation. They seemed convinced it was a thing that would happen only rarely, even as we were telling them otherwise. Anyway, that 1 cent charge just infuriated clients for some reason, and every month or two we'd get bug reports about it or requests to explain why it kept showing up. Fun times...


Why was it listed separately at all?

If the month's charges were 406.783228 and I get a bill for 406.79 then that seems perfectly good.

If I get a bill that says 406.78, plus a separate 0.01, that's weird.


> Why was it listed separately at all?

Because the bill included a detailed breakdown of all the fees by type and transaction. Typically our clients were charged a fixed monthly fee, a fixed authorization fee charged every time a payment was attempted (even if it was declined), and a fee applied to successful payments that was a percentage of the payment amount. There were other fees for things like processing chargebacks, but IIRC they were the same for everyone.

> If the month's charges were 406.783228 and I get a bill for 406.79 then that seems perfectly good.

Yeah, we definitely weren't allowed to round up and keep the change. I can't claim to know all the details involved but I suspect that doing so would've at least violated our contract with the payments gateway. Might've actually been illegal.

> If I get a bill that says 406.78, plus a separate 0.01, that's weird.

That's not how it worked. For the sake of simplicity let's assume that your activity is always the same, therefore you have new charges totaling exactly $406.783228 every month.

* Month 1: You owe $406.783228, your bill is $406.78, a balance of $0.003228 rolls over.

* Month 2: You owe $406.786456, your bill is $406.78, a balance of $0.006456 rolls over.

* Month 3: You owe $406.789684, your bill is $406.78, a balance of $0.009684 rolls over.

* Month 4: You owe $406.792912, your bill is $406.79, there's a $0.01 "other fee" line item on the bill, and a balance of $0.002912 rolls over.


It's unlikely that it would be illegal, or violate the contract with the payments gateway, if you charged the customer 406.78, and you made up the $0.003228 discrepancy so the gateway is paid off.


I wasn't talking about rounding up. I meant exactly the same thing you're saying about month 4.

> Because the bill included a detailed breakdown of all the fees by type and transaction.

Was it all rounded [down] to the nearest penny?

That type of bill can already fail to add up to the total very easily, like x.xx4 + x.xx4 + x.xx4. So I'm still not sure why there was a need to have a line item to explain this single penny.

Was there only a single charge on each bill that used fractional pennies? So that this was the only time that things wouldn't add up perfectly?


Canada abolished the penny some years ago. If some retail item in a store costs $2.03 and you insist on paying with cash, you will have to use a nickel and pay $2.05.

Yet, life goes on; nobody goes to jail.


Generally such rules are only about cash, though.


> Anyway, that 1 cent charge just infuriated clients for some reason, and every month or two we'd get bug reports about it or requests to explain why it kept showing up.

There is an obvious alternative here, which is to just absorb that fraction of cent, so the customer doesn't see it. Handling bug reports and infuriated clients costs more.

You could even just round up to the penny and ding the customer; if there is a 0.3 cent fraction coming from the payments gateway, turn it into a customer-facing penny, thereby collecting an extra 0.7 cents.

That way, too, there would almost certainly be zero complaints and bug reports.

If the customer is supposed to pay $103.45395 every month, you could turn it into $103.46, pocketing an extra $0.00605, or into $103.45, where you're out $0.00395.

Think about it; when you eat at a restaurant, often the prices for a meal are round like $11.50, even though the restaurants expenses are down to the penny. Why is that? Because they arbitrarily set the price. They don't say, oh, our ground beef supplier charges to the penny penny, so lunch will have to be $11.57.

Oh, the business teams found the approach puzzling---but what do they know, right? If they were smart, they would be software engineers.


> Oh, the business teams found the approach puzzling---but what do they know, right? If they were smart, they would be software engineers.

Well in this case the business teams selected the 3rd party payments gateway that the company would work with, negotiated the contracts with them, and worked with the 3rd party to set up how the customers would be charged. They and/or the product team determined that we'd use the 3rd party system to handle the billing because building it in house wouldn't generate any new revenue. They weren't stupid, but they choose an approach to payments processing that was pretty low level (because it generated more revenue, of course), without anyone at the company having a good understanding of low level payment processing details. The engineers learned because we kind of had to, but three years into the project (when I left) business/product would still routinely struggle with the details.

So anyway, WRT to billing, the task handed to engineering was: pull the billing detail from the 3rd party's API and assemble it into a statement that can be handed to a client. We had zero control over how the charges were generated or applying any rounding to the total.


If someone had a gun to my head saying, don't hide the sub-penny slices from the billing, I would just do the billing in thousandths of a cent, rather than make "leap cents" appear every couple of bills:

  Amount owing: $123.45678

  Please pay one of: $123.46 (a credit  of $0.00322 will be applied)
                     $123.45 (a balance of $0.00678 will carry forward)
On the next statement, if they paid $123.46:

  Previous balance: (  $0.00322) [credit]

  New charges:        123.45678  { here we have a detailed breakdown }

  Amount owing:       123.45356

  Please pay one of: $123.46 (a credit  of $0.00644 will be applied)
                     $123.45 (a balance of $0.00356 will carry forward)
etc.

That's literally "put the billing detail from the 3rd party API and assemble it into a statement". Since the billing detail from the 3rd party API is in thousandths of a cent, then that implies the statement must have thousandths of a cent.

If the two payment options were determined to be too confusing, one of the two could be dropped.


> I would just do the billing in thousandths of a cent, rather than make "leap cents" appear every couple of bills

Again, that was not something we could control.


I think that's a case for just dropping that cent. As long as it's your own money and not tax you're underreporting it's not a problem as long as it's consistent.


Carrying the remainder microcents as a bill seems overkill and not necessarily correct. Accounting regulations and accepted practices have well established rules on rounding, like bankers rounding (1).

You could probably treat the extra microcents as being on the next pay period. Though that's annoying as if I close an account I'd expect it to be paid in full, not a few microcents remaining.

1: https://stackoverflow.com/questions/45223778/is-bankers-roun...


>A shocking number of people are unaware of how many decimal points of accuracy their local tax code requires to calculate vat or sales tax correctly.

What is your jurisdiction? In Canada, I can't for the life of me imagine the CRA would remotely care about decimal-point accuracy. In fact, most of their online forms explicitly remove the decimals.


UK. The rules may have changed now, it's a long time since I implemented the rounding rules here, but the last time I did it required 5 decimals accuracy. The rules also used to specify how you needed to account for line items vs. sub-totals in your invoices to ensure you didn't find any "workarounds" to shave off some pennies of tax (In fact, the last time was while the tax authority was still called the Inland Revenue, which it hasn't for years.)

For aggregate totals of your VAT liability across your total set of invoices, you'd be fine with rounding up to the nearest pound, to the Inland Revenue's benefit. For individual invoices however, you were required to stick to very specific rounding rules.


> For aggregate totals of your VAT liability across your total set of invoices, you'd be fine with rounding up to the nearest pound, to the Inland Revenue's benefit

On personal tax forms you have to round in the taxpayer favour. If your income is 12345.67 you round it to 12345. If your expense (say giftaid) is 12345.67 you round it to 12346.

Surprised it's the other way with VAT, but then I do very little with tax other than click a few buttons and confirm "yes, you have to tax me as I have children".


I'm not sure it's the other way with VAT, as I said it's been many years and the rules may well have changed multiple times.

The key, though, is that with taxes, if at all unsure you've got the rules right, the safest option is to round in the tax offices favour.

It's in general a lot less painful to explain an overpayment than underpayment if something is broken.

Of course, better yet, get it right.


That's for final amounts though, right?

I bet they care about you not throwing away decimals in intermediate calculations for VAT or sales tax.


I think the point is that the integer arithmetic implementation your CPU provides is wrong in at least one jurisdiction, so (for example) the machine code in the article is wrong.


> done something grossly incompetent like used floats for those calculations

So, in another life I worked on reporting software for a foreign branch of a US bank. You've heard of the bank. You would probably recognize the CEO's name, in fact.

We had been fucking this up for years. I fixed it. We had some customers who yelled at us because our reports were "wrong" i.e. they were double checking our work and apparently making the same mistake. They could not be reasoned with. Bear in mind, we're talking about differences of pennies, or a few dollars on very large transactions. Some of our customers insisted we were calculating the values incorrectly and demanded we "fix" it.

What do you think happened next? You have one guess.


> A shocking number of people are unaware of how many decimal points of accuracy their local tax code requires to calculate vat or sales tax correctly.

I would imagine almost no-one knows this (-: What's a shocking number?


> What's a shocking number?

Almost every developer I've worked with who haven't implemented invoicing or billing at least once and had their finance team yell at them for producing wrong numbers...

(and I'll edit this to add the limitation "who implement billing related software" - it's still true, and closer to my intended point)


> A shocking number of people ... are unaware of how many decimal points of accuracy their local tax code requires to calculate

A shocking number of people who create tax codes have no idea how many decimal places they are using.

It's probably better now, but I recall having to reverse engineer the tax tables to figure out how many decimal points of accuracy were used, and what rounding rules were used, so we could match their numbers.

These numbers would change from year to year, with no change in the underlying tax codes.


I always hated tables. At least when I last had to implement UK VAT rules the rules were very precisely defined. But I've had to deal with stupid tables implementing rules they couldn't be bothered to spell out before. Yikes.


in my experience, these are often written off as error.

if the error is less than their hourly salary rate, it don't even worth mentioning.

if it worth a day or two of salary, its nice to fix but never a priority


For it to be written off as error you need to know the discrepancy, which means you need to know what it's supposed to be. When e.g. calculating the VAT or sales tax you owe the government, if the rounding deviates from legal requirements then unless it's in their favour you can be in for a bad time.


I think for most large businesses there are pretty considerable error bars here. If you say you owe 1,000,000 a year in VAT and the government says you owe 1,010,000 it's cheaper to pay the difference then dig into why it's off.


If you report 1,000,000 and certify that it's the right number, and the government audits you and find you should have paid 1,010,000, then depending on jurisdiction you might be entirely ok, or you might find you're not going to be paying just the difference, and interest, but also a fine, and bearing the cost of additional audits going forward, and that your finance director will not appreciate having to address questions aimed at figuring out whether anything criminal is involved. Repeat the mistake a few times, and the level of scrutiny will escalate.

There's a reason that in 28 years of working in software, the only thing the financial teams I've worked with have obsessed over have been whether or not we get the VAT calculations right, and the "sticker price" of the discrepancy has never been what they worry about. For calculations that does not involve getting tax amounts wrong, they often couldn't care less about much bigger discrepancies, but get tax wrong in the wrong jurisdiction and it's a lot of pain.


It's strange. In Russia, small error in VAT will get you a letter from tax service "pay us a small error voluntarily, or we will schedule an inspection". Letter will be automatically generated by ASK-NDS system (translated as Auto Check Vat).


How do they know there's an error?


Cross check with your contragents. For every bit of incoming VAT should be outgoing VAT from your supplier. And for outgoing VAT should be incoming VAT for your client and/or sale to physical customer.

If your incoming VAT are not matched with outgoing VAT from your supplier, you will be charged.

If your supplier declared VAT, but failed to pay it, you have choice: either you have to pay it or you will be inspected to proof that it was not a fake.

Every sale to physical customer in Russia should be uploaded to tax service cloud. You (as a customer) could check your receipt online or using app, and get a reward for reporting tax evasion.

This system boosted VAT revenue x1.5 in a few years.


Why would it be wrong to use floats?

That would have been my default assumption


When you're doing floating point arithmetic on a computer, it will approximate and round certain values in ways that don't match the way humans do it when they're, e.g. doing accounting.

So you need to run a massive physics simulation really fast? Yes, floats are great.

You need to calculate taxes on a massive corporation's fiscal year? Bad idea.

Some libraries advertise "arbitrary precision", many computer systems have a "decimal" type intended for currency, etc. and then they won't make all the same mistakes, but as the OP said you still need to control rounding rules and make sure they match the law.


> You need to calculate taxes on a massive corporation's fiscal year? Bad idea.

That depends on whether the hundred-billion-dollar corporation cares about being off by a dollar.

And by "off" I mean "different from how humans round", not necessarily further away from an infinite-precision calculation. In fact at "massive corporation" level I would guess that binary floating point is more accurate than a typical fractional penny system.


> That depends on whether the hundred-billion-dollar corporation cares about being off by a dollar.

How many hundred billion dollar corporations are private? Public companies would care a great deal about accounting accuracy.


Is it worse for a hundred billion corp to be off by a dollar than for a hundred million corp to be off by a third of a penny?


it's not so much how much it is off but that it's off at all. If the numbers don't add up then they don't add up. If there's any kind of difference then it has to be found and accounted for and it becomes a needle in a haystack search to account for the difference. Think about trying to find $0.05 spread across hundreds of thousands of transactions due to rounding issues.


I'm going to let you in on a secret...

Every single publicly listed company, every single one of them, is off when it comes to calculating their taxes by way more than just a dollar. And I don't mean clever accounting tricks or tax avoidance schemes, I just mean in terms of actual mistakes being made.


If they could just pay the dollar and never have to worry about it again, sure. But the point is for them to have confidence that the math is unimpeachable and identical to whatever auditor or tax official would compute at every step of the way so you don't just have to guess at correctness with some waving of hands.


Surprisingly common values like 0.1 don't have a precise representation in binary for most formats, including standard floating point number formats. See https://0.30000000000000004.com/ for more detail than you can shake a stick at.

Also if the local tax code states using 5 decimal places for intermediate values when you will introduce “errors” using formats that give greater precision as well as those that give less precision. Having worked on mortgage and pension calculations I can state that the (very) small errors seen at individual steps because of this can balloon significantly through repeated calculations.

Furthermore, the name floating point gives away the other issue. Floating point numbers are accurate to a given number of significant figures not decimal places. For large numbers any decimal places you have in the result are at best an estimate, and as above any rounding errors at each stage can compound into a much larger error by the end of a calculation.


IEEE standard floating point uses a binary mantissa.

And binary has trouble representing fractions that are common in prices:

  $ bc
  obase=2
  scale=20
  1/5
1/5 in binary is a repeating binary fraction: 0.0011001100110011...

Just as you can't express 1/3 or 1/7 precisely as a non-repeating decimal fraction, you can't express 1/5 and 1/10 as a non-repeating binary fraction. As a result, most prices involving cents in currency cannot be expressed precisely as binary floating point numbers.

edit: fixed formatting


The biggest issue is you now need programmers who know about epsilon computation and error propagation when working with incorrect numbers. Then you need to know when to fudge the visual representation of your incorrect number (and you probably also need to understand when your programming language / libraries do fudge the output for you).

FP numbers have their use but they re better reserved for scientists doing actually scientific stuff and not just to represent what are actually tiny numbers (in the grand scheme of things) and which can be represented perfectly by other means.


If there's an applicable law or regulation that says "you must do x", and you do y (and that yields different results), you'll get into trouble, even if your way yields "better" or "more accurate" results.

This is not to say that using floats and rounding correctly necessarily does yield different results, by the way (although most likely it will) – but if they do differ, you're going to have a bad time using floats.



> 0.3-0.2-0.1

-2.7755575615628914e-17

And now you overdrafted


Floating point calculations without some final rounding step before presentation/export/storage are almost always wrong, since you're implying much more precision than is justified by your source data.


Ths problem isn't rounding the final result. The problem is that the source data itself can't be accurately represented.

There is no floating point value equal to 0.3.


That’s not a problem by itself.

You can represent 0.3 as 0.300000…0004, which rounds to 0.3 again in the end.

But you need to reason about the number and nature of intermediate operations, which is tricky, since errors usually accumulate and don’t always cancel out.


> That’s not a problem by itself.

No, it really is the original sin here.

> since errors usually accumulate and don’t always cancel out.

The problem is that from the system's perspective, these aren't "errors". 0.3000000....4 is a perfectly valid value. It's just not the value that you want. But the computer doesn't know what you want.


> The problem is that from the system's perspective, these aren't "errors".

When I say "error" here I mean the mathematical term, i.e. numerical error, from error analysis, not "error" as in "an erroneous result".

There is a formalism for measuring this type of error and making sure it does not exceed your desired precision.

> It's just not the value that you want.

My point is exactly that if you're looking at 0.300000...4, you aren't done with your calculation yet. If you stop there and show that value to a user somewhere (or are blindly casting it to a decimal or arbitrary precision type), you are using IEEE 754 wrong.

You know that your input values have a precision of only one or two sub-decimal digits, in this example, so considering more than ten digits of precision of your output is wrong. You have to round!

It's the same type of error that newspapers sometimes make when they say "the damage is estimated to be on the order of $100 million (€93.819 million)".

Yes, this is often more complicated and error-prone (the human kind this time) than just using decimals or integers, and sometimes it will outright not work (since it's not precise enough – which your error analysis should tell you!)! But that doesn't mean that IEEE 754 is somehow inherently not suitable for this type of task.

As a practical example, Bitcoin was (according to at least one source) designed with floating point precision and error analysis in mind, i.e. by limiting the domain of possible values so that it fits into double-length IEEE 754 floating point values losslessly – not because it's necessarily a good idea to do Bitcoin arithmetics using floating point numbers, but to put bounds on the resulting errors if somebody does it anyway: That's applied error analysis :)


Do it a million trillion times and we're talking cents overdrafted (almost)


If a rounding error put me a million-trillionth of a cent into my overdraft, I’m pretty sure my bank would still activate that $20/mo overdraft fee :P


If you just add up the errors, sure. What is riskier is that you risk tipping values the wrong direction right before applying a rounding step, or end up with an error right before multiplying a now wrong per-unit value with some large-ish factor.

Often these things are not a big problem on their own, but then later gets compounded because someone does something stupid like passing these imprecise values around to be distorted further all over the place.

And sometimes the reason it doesn't become a legal problem turns out to be because your finance department quietly works their way around it by expending expensive manpower accounting for discrepancies that shouldn't be there in the first place, and so increases the cost to the business by many magnitudes over the loss the developers might have assumed to be the worst case (if they're aware of the discrepancy at all).

This is one of those things you can get away with many times, many places, with no ill effects. But when it finally bites you it can get expensive and/or really bad to deal with, and it's fixed by simply never doing money calculations on datatypes with imprecise arithmetic, and having a five minute conversation with your finance team about what your local rules for rounding tax amounts are.


- "or end up with an error right before multiplying a now wrong per-unit value with some large-ish factor."

Where in financial accounting do people multiply an amount of money by a multiplicand larger than order-of-unity?


In accounting, no, while preparing input to the accounting in the form of generating invoices, I've lost count (sorry) of the number of times I've seen people doing tax calculations etc. on unit prices and then multiplying by number of units ordered, and then further compounding potential issues by adding up these numbers from multiple invoice lines. None of which is usually the right thing to do, all of which you often "get away with" without causing sufficient discrepancies, and so which people often fail to catch in testing. Until you suddenly don't.


- "multiplying by number of units ordered,"

Yeah, that's one example. I wasn't imaginative enough; thanks!


Decimal systems have to round too, so that's a pretty weak dismissal.


Laws are typically written by humans, and we use base 10, not base 2. We think $0.03 is an exact number, but floats can’t represent 0.03 exactly.


I think the historical interpretation is also relevant. The systems that did accounting before digital computers used base 10, so the first computerized systems for accounting used base 10 also. This legacy extends to the point that mainframes often had (and I believe still have) special decimal floating point math instructions. There have been several ways to accomplish this BCD (binary coded decimal) where numbers are stored in base 10 using a 4 bit encoding. I believe this can be arbitrary precision, but don’t have any experience myself. Some hardware also has decimal32 and decimal64 floating point hardware, which is part of recent versions of the ieee754 spec[1]. Databases also often have a DECIMAL type for doing calculations on money values [2]. So I think it’s not just that laws say it should be a certain way, but also that it is important to maintain consistency between systems over time.

1: https://en.wikipedia.org/wiki/Decimal64_floating-point_forma... 2: https://dev.mysql.com/doc/refman/8.0/en/precision-math-decim...


Floats lose precision unexpectedly with certain fractions that are perfectly representable in decimal, and also with certain integers once you get high enough.

The standard in ad-tech (not sure about banking) is to use int64s representing either microdollars or microcents, so a max capacity of 9.3*10^13 or 10^11 dollars


floats are an imperfect representation of real numbers and as such, there are an infinite count of real numbers that cannot be accurately represented with floats (and doubles).

It gets even worse when you start doing calculations on floats/doubles.

These inaccuracies are ok for a lot of things. graphics often uses floats and the errors are small enough they don't matter.

But currency absolutely needs to be accurate, and for that reason, floats/doubles are in appropriate.


Floats are not how you do money math, if you run into anyone trying to do money math and they say they use floats, there is a 99.999999% chance they are wrong.

> Maybe you value arithmetic correctness over simplicity of procedures

Lots of places(not typically the USA) has this codified in law. For example, do a web search for: EU money rounding rules. You will find several different rounding and precision rules, depending on the context of what you are doing with the money, all from places like the Central Bank and the EU Commission.

It's mostly US developers that are clueless here, because US laws are fuzzy at best, and the general rule is, you do whatever your bank/regulatory authority does, and if they don't happen to know (and I've met several that don't), then you have to figure it out yourself.

In the USA, we use decimal.ROUND_HALF_UP, because we have seen in practice this is what our USA based banks & govt tend to do in the wild. It should be noted IEEE 754 rounding recommends using decimal.ROUND_HALF_EVEN. https://en.wikipedia.org/wiki/IEEE_754#Rounding_rules

In other places, we do whatever their laws require, or treat them like the USA and do whatever our local bank/govt authority tends to do in practice.


I agree with the parent. Almost everyone thinks floats are fine. I work in lending, and many of my coworkers, who claim to have CS degrees, do not understand floats at all.


GP isn't just saying "don't use floats", though. (And even that is only a heuristic: It's possible to get correct results using floats, but you need to be very diligent about when and how you round, so in practice it's easiest to just avoid it.)

They're saying that only arbitrary precision arithmetics are acceptable, and additionally claiming that everybody else in the world gets money arithmetics wrong.

I doubt both of these statements, and especially the assertion that there's exactly one "correct" way of doing arithmetics with money.


Arbitrary precision decimals are the best solution in most cases. I have worked at multiple companies where people represent currency with floats and then wonder why they get strange results in some cases.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: