I decided years ago that the next time I hear someone suggesting we use floats / doubles to represent money amounts, I am going to punch them in the face.
This gets repeated a lot, and I don't disagree. But I find odd that doubles would be so unsuitable for monetary (and other similar) arithmetic; in principle you have 15 significant digits which should be more than enough, and precise control how the results are rounded. And all the basic arithmetic should return correctly rounded values to the last ULP. So it is weird that those tools are still not good enough and it is also difficult (at least for me) to fully characterize why exactly they are not suitable.
Part of me wonders if this (justified!) fear of floats is in part because a history of bad implementations (looking at x87) and difficulties in controlling floating-point env (looking at libs randomly poking fpenv), and less due floats intrinsically being bad.
The problems of floats are not the number of significant digits, it’s the imprecision of the representation (floats don’t just cut off at the end), that these imprecisions compound, and that float operations are not commutative. At the end of the day, 0.1 + 0.2 != 0.3 is a fact you have to live with.
X87 does not really factor into it, if anything in your view of the world x87 floats would be better since x86-EP is 80 bits. Except its involvement now leads to intermediate-precision-driven inconsistencies.
Control (which you mention) and consistency are the issues, as well as the interaction between that and comparators.
Guarding against floating-point issues or considering precision errors is neither part of school-learned arithmetics, nor of most CS programs, to almost every developer just flings around floats like they’re genuine reals, and when problems start surfacing floats are so threaded through without consideration it becomes very hard to untangle, which leads to local patch jobs which make the problem worse.
> At the end of the day, 0.1 + 0.2 != 0.3 is a fact you have to live with
That is the one example that floats around a lot, but its also imho not very good one. '0.1', '0.2', and '0.3' are not floating point values, so the premise is flawed.
Also `round(0.1 + 0.2, 15) == 0.3` is true (in python), so being conscious about rounding things appropriately goes long way. And I imagine that correct rounding is relevant in monetary calculations no matter what sort of numbers you are using, so while while floats the situation might be more pronounced I don't see it being such fundamental problem.
> That is the one example that floats around a lot, but its also imho not very good one. '0.1', '0.2', and '0.3' are not floating point values, so the premise is flawed.
No, it’s the entire point. None of the values we deal with day to day are binary floating point, and certainly not currencies. So this sort of representational approximations is a major and constant issue of using floats.
> Also `round(0.1 + 0.2, 15) == 0.3` is true (in python), so being conscious about rounding things appropriately goes long way.
See above, rounding off and collecting error after every arithmetic operation is not the expected norm and what developers are taught.
> And I imagine that correct rounding is relevant in monetary calculations no matter what sort of numbers you are using
While that is true, it does not normally need to be done after every arithmetic operation, especially not after additions of already rounded off values.
> See above, rounding off and collecting error after every arithmetic operation is not the expected norm and what developers are taught.
Question is, is that a problem with developers or floats? :)
Ecosystem and tooling might help here, iirc that is something Kahan himself has been complaining about a lot. For example hypothetically you could have something like FP contexts or specialized high-level types where you can easily express how many digits are you expecting and the runtime/compiler would manage rounding etc so that you'd get more often correct results. Tbh that's just top of my head, and I didn't think too much about it.
But I think the question remains, how much of the problems are actually intrinsic to FP, and if you did actually cross t's and dot i's then would there still be some intractable problems in using FP with money? So is the problem "just" that FP can easily be misused, or that its impossible to use correctly?
I want to emphasize that I do not recommend anyone go using FP for money now. I'm just curious because its something I don't fully understand and well, HN has smart people that can hopefully help me there.
Question is, is that a problem with developers or floats?
Of course with floats. Requirements come from decimal-expecting people and developers have to convert requirements into an algorithm. If there’s a fundamental semantic or at least syntactic obstacle, it’s not a problem with developers.
Iow, if a language/system only has floats as “numbers”, it sucks for most business-level calculations.
> and that float operations are not commutative. At the end of the day, 0.1 + 0.2 != 0.3 is a fact you have to live with.
Addition and multiplication of floating point numbers is commutative (sole theoretical exception: there principally exist multiple representations of NaNs, even though in practice processors do not make use of this freedom, i.e. in practice these floating point operations are completely commutative).
And that’s a problem, now you have to round-off defensively which complexifies the code and you have to decide how often and how defensively you round things.
Plus you’ve got the added fun that fp rounding routines don’t necessarily take a rounding mode.
You can get a bunch of bits, but how you handle it make a ton of differences
Plus, so little languages care about us living in the business world. You can count with fingers in a single hand the languages that are meant for business app.
All the others are bad languages (and libraries, frameworks, data stores), ill-suited for the job. And so, all of them need to reimplement (badly, ad-hoc, bug-ridden) version of money and friends.
And weirdly, no hardware support at all, so our friends on C say: "Look, no important to handle money, bye" and nobody else does it either.
If you’d ever had to bill millions of customers for precise amounts of electricity and gas at precise prices… you would hate floats and you’d hate that any idiot will act as though excel is gospel truth.
That's also the case with integer or fixed-point calculations. You generally don't care about the accuracy of specific calculation (unless it results in edge cases like a catastrophic cancelation in floats), but you do make sure that the resulting invoice is free from any sort of numeric artifacts like the sum of ratios equals to 99.9% or 100.1%.
Why would you? For most forward-looking calculations, the uncertainty of the future completely swamps any cent-rounding.
Even for plain-vanilla bond price calculations, floats are the right tool for the job. Say you have a bond that pays $5 every year for 10 years, then $100. What's that worth today?
Well, you have a forecast yield curve of interest rates. Say it's quoted as continuously compounded rates, so then you get something like price = sum_{t=1..10}($5*exp(-r(t)*t)) + $100*exp(-r(10)*10).
But wait, say you actually have 1000 different potential paths of interest rates, and you want to average over all of them.
Oh, and there's a 1% chance of default every year.
Oh, and actually these are mortgages, so there's a path-dependent chance of them refinancing every year, if the rates get low enough.
And then there's an overall economic forecast, so if you have a bunch of mortgages, there's a bigger chance they'll all default at the same time.
And so on. Rounding the cents isn't really worth the worry, once you're putting noisy forecasts through `exp` (or worse special functions).
This applies for vanilla bond valuation, any option, any future. More so if you want risk measures (what if rates go up 0.10%? volatility increases?), and so on.
This is true in complex scenarios but not true in other finances scenarios. For example, there is a reason why any electronic exchange will use integers with implied decimal precision as the wire format and will continue to use such representations before and after encoding/decoding. We do not need to do hugely complex operations, it is mostly simple comparisons and some simple maths operations. We absolutely need exact precision and speed and it is difficult to get that when using doubles.
In parts of the stack where things are more complex and outside of the critical path, then yes, you use floating point.
Also, it isn't just that rounding the cents isn't worth it, it is that if you work with implied decimal integers with an implied 2DP, then you're going to end up with massively inaccurate results after a few operations.
I heard that countless times, and I understand the reason (mainly: floats are binary, money amounts are decimal), but then, how do you split a $10 bill between 3 people.
$3.33 for each is not good, because in the end you have to pay $10, not $9.99. You can use a double, which is more precise, but in a less predictable way. You can use factions, which is exact, but it may become unmanageable after some time. Or you can have one of the three pay $3.34, but which one? I guess there are rules for that, probably a lot more complicated than "use an integer number of cents".
If you have a few grown-up adult parties, e.g. cofounders, partners, then split like [(n-1) x round(total/n), whats_left]. The last one is how sql select sees it.
If you have potentially penny-hysterical kinds (taxes, anonymous group customers), round in their favor and throw pennies into your own expenses.
If n ~~ total, e.g. $100.00 over 700 people, don’t do that, it’s bad accounting.
I worked with finance and accounting half my life. They just don’t fall into these philosophical dilemmas.
We use long double to present financial money amounts with a little safety on top of it before it’s consumed by whatever JavaScript (Typescript really) frontend it heads to. Works fine. Outside of the need for speed it’s one of the few areas we use c in our backend services.
We don’t store the data in floats or anything resembling it, however.
It depends we sometimes do since quadruple precision with checks tends to be safe, but for the most parts we don’t as most things are basically transactions unless you need to display something.
Please stop repeating this nonsense. It's purely based on ignorance and only pushes more people into it.
This is universally repeated by people that have not written any modern financial software and who don't understand floats.
If all you do is add US dollars and pennies, then maybe you can get by with integers. Once you do anything else in modern finance integers puke completely. There's a reason scientific computing doesn't use integers, but prefers floats/double - they are much easier to use to get the best answers per compute.
For example, if you need to do anything with interest, which is fundamental, you'll soon find doubles are vastly better than bitsize equivalent integers, no matter what scaling/fixed-point/other tricks you employ. Add in currency differences (Yen to USD is a large multiplier, etc), any longer range calculations (50-100 year loans or flows), aggregation of varying items, derivatives, and on and on. Telling anyone in the field you're going to use integers will get you laughed at - the problem is not floats, the problem is the programmer hasn't taken a single class on numerical analysis and has not enough skill to do financial software.
For example, one of the simplest things one needs to do is compute compound interest, say computing mortgage tables, with say principal P (left), annual (or weekly, or daily..) rate R, for N years, periodic payment m, and you want to compute payments and schedule.
In reals, this is simple: each cycle you do something like
r = (1+R/12)
P -= m
P = (1+r)*P
Then you write out values you need.
Trying to write this software with integers, no matter what scaling, fixed-point, shifting, and other tricks you employ, is going to be vastly more complex and error prone than simply using doubles. If you don't think so, pick a bit budget, say 32, or 64, for you base number type, and show me your code. Then I'll show you the naive one with doubles vastly outperforms your code.
This continues through all of modern finance.
So stop repeating this ignorance that one should use integers for money - that is purely a result of being ignorant about how to write robust numerical code, and only pushes people down a far worse rabbit hole when they hit issues with ints and try to patch them one at a time via ad-hoc hacks.
There's a reason scientists use floating point, not ints, to do real numerical work.
Assuming you want your money to actually add up correctly, then floats are always the wrong choice.
If you’re not interested in accurate accounting, the. Sure floats are fine, but when you’re working with money, accurate accounting tends to be the expectation.
Addition is one of those things that does work pretty predictably and error free with floats. The problem with 0.30000000000000004 etc is usually that the things you are adding are not what you expect (float(0.1) != 0.1), i.e. the difficulty usually is string<->float conversions rather than float arithmetic itself.
Even that error is a problem. Doing a single addition might be fine, but doing thousands or millions of additions, and those tiny errors add up to something appreciable.
If you’re doing money operations at a scale where the computational difference between using true number type with infinite precision vs floats is worth thinking about, then you’re also in the territory where tiny floating point errors really stack up into a problem.
As a consequence, there’s very few scenarios where using floats for money actually makes sense. Either your computation is so simple there’s no real benefit to using floats rather than infinite precision number types, or your computation is so large, that floating point errors sum to meaningful amounts.
The only scenario I can think of where floats might be the right choice is on tiny embedded system where computational power and memory is very limited, and working with infinite precision types is a real problem. But in that scenario, you probably don’t need to be educated on the issues with floats. But if you’re the kind of person who unsure if you should use floats for money, then the answer is almost certainly a resounding “No. Do not use floats for money values”.
I would argue the more correct answer is if you don't know what maths to use for money (including e.g. any legal rules about how you do it), you shouldn't be doing maths on money.
That strikes me as an unnecessarily elitist answer that holds us back.
I'm hardly a mathematician, or even college educated, but AFAICT this all boils down to the fact that I can type a number into the computer, and it can't represent it exactly internally, so it misrepresents it, silently.
Where I come from, that's called "a bug", regardless of cause.
Non-mathematicians (and even non-accountants and non-financiers and the like) have to math money all the time. They do it in daily life. Some of them even write programs to do it, because they know enough about computers to do that.
They don't expect that their expensive smartphone is going to screw up the calculation due to some esoteric representational reason that they need four or eight years of college to be aware of, let alone to understand or explain.
And they shouldn't need to!
I would argue that if computers can't do the job correctly in every case without the user jumping through hoops, then we should be continuing to develop methods to make it better.
string->float conversion is conversion from accounting realm to computer abstraction. If the conversion is not accurate and the results can not be converted back into accounting realm without artificial artifacts the use of such computation is problematic.
The conversion is accurate up to 15 digits. Round-tripping through floats should not cause artifacts if you know what you are doing and remain within that 15 digit bound all time, and I believe that same applies for all basic arithmetic operations. So the question is, what are actually the cases where floats introduce "artifacts"
Mostly. You might be surprised how much of a headache it creates when things are off by a little in ways customers don't understand even when you're not settling transactions like that. Customers get confused when they get receipts or invoices where things don't add up, even when it saves them a penny! I've seen rounding down make people mad because it didn't add up when discounts were applied even when they were the benefit of the extra cent. Obviously rounding up makes people mad out of principle because it's adding cost that wasn't agreed upon.
I have left another comment. A trillion times this. Floats anywhere give two outcomes: customer accuses you of salami slicing from them or you give away millions.
Part of me wonders if this (justified!) fear of floats is in part because a history of bad implementations (looking at x87) and difficulties in controlling floating-point env (looking at libs randomly poking fpenv), and less due floats intrinsically being bad.