10 ms·
Your balance is $0.30000000004
- vivan 7y agoYou would think that this is a fairly rookie error and that big companies would know better, but I regularly see this on Uber: https://i.imgur.com/qDACtG0.png https://i.imgur.com/qDACtG0.png
- ryandvm 7y agoOn the surface one would think big companies should "know better". I mean, a large company is the accumulation of centuries or even millennia of career experience. Unfortunately, they also contain just as much accumulated nonsense and foolishness; and enough bureaucracy and organizational cruft to hide it forever.
- saagarjha 7y agoPlus there’s the issue that the career experience isn’t evenly distributed, so you can often have people implementing features without the background necessary to do them right.
- mark-r 7y agoA large company is the accumulation of a large number of people, some of who will know better and some of who will not. Hopefully the company is organized such that the people who know will be in a position to catch those kind of errors, but that's not guaranteed.
- raxxorrax 7y agobrowser console: (1.005).toFixed(2) (1.005).toFixed(20) reveals the problem. Math.round(1.005 * 100) // wrong In the end these conversion errors are not solvable in any language, so you have to "cut off" somewhere. There are different approaches to this. Wasn't there a case where programmers stole the "wrong" cent and wasn't The Office a persiflage on that?
- vivan 7y agoThat was the main plot of Office Space. https://www.imdb.com/title/tt0151804/ https://www.imdb.com/title/tt0151804/
- davedx 7y ago> In the end these conversion errors are not solvable in any language Not true at all. Ruby has BigDecimal: https://ruby-doc.org/stdlib-2.5.1/libdoc/bigdecimal/rdoc/BigDecimal.html https://ruby-doc.org/stdlib-2.5.1/libdoc/bigdecimal/rdoc/Big... .NET also has a Decimal type. I've worked on salary calculation applications and e-commerce platforms, and found that language choice makes a big difference.
- mark-r 7y agoIt's not that they're not solvable, it's just that you have to go out of your way to use the proper type or library. The default floating point type still has problems.
- aeyes 7y agoUber does not use float for money but the internal representation is converted to something displayable in the API layer. Normally strings are used, for example: https://developer.uber.com/docs/riders/references/api/v1.2/requests-request_id-receipt-get https://developer.uber.com/docs/riders/references/api/v1.2/r... Guess somebody messed up for this case.
- vivan 7y agoIn this case, the value is the "surge_multiplier", which is indeed a float: https://developer.uber.com/docs/riders/references/api/v1.2/estimates-price-get https://developer.uber.com/docs/riders/references/api/v1.2/e...
- mason55 7y agoInterestingly even in your link you can see that most of the money values (subtotal, total_fare, total_charged) are strings but for some reason total_owed is a float. Presumably someone messed up but it wasn't caught in code review.
- tonyedgecombe 7y agoI've actually seen it on a statement from my bank.
- francisofascii 7y agoGood example of a leaky abstraction. JavaScript developers almost never have to worry about or completely understand the intricacies of floating-point arithmetic, until errors like this happen. https://en.wikipedia.org/wiki/Leaky_abstraction https://en.wikipedia.org/wiki/Leaky_abstraction
- marklacey 7y agoI wouldn’t consider this a leaky abstraction. Floating point numbers are explicitly not trying to be an abstraction for real numbers, but rather an approximation for them that is good enough for most purposes (if you’re careful in how you use them). That they might not be taught as such is another problem.
- beatgammit 7y agoExactly. Everyone dealing with JavaScript should be taught about .toFixed(num) for rendering numbers as strings. In fact, anyone working on front end code in any language should learn that language's number formatting tools (e.g. sprintf). There just isn't an excuse for this type of sloppiness.
- OskarS 7y agoThis is not a leaky abstraction. JavaScript doesn't pretend that the numeric type is anything other than an IEEE-754 float. It would be a leaky abstraction if JavaScript said "this type can represent all real numbers!" but that would be insanity.
- deleted 7y ago[deleted]
- kerkeslager 7y agoSure, it's not a leaky abstraction as long as what you want to represent is an IEEE-754 float. Let me know if you ever come across a real-world case where that's exactly what you want. Sure, that's a good-enough abstraction for a lot of cases, which is why a lot of languages implement that standard, and it's pretty reasonable for JS to do that. The pedantic argument you're making is basically that it was never intended as an abstraction. But when it's used as an abstraction, it's almost inherently a leaky abstraction, and it's almost always used as an abstraction.
- huseyinkeles 7y agoMartin Fowler's money pattern [0] solves this problem. Just make sure whenever you're dealing with currencies to use a value object for money, rather than float. Lots of languages have libraries for this pattern. [1] [0] - https://www.martinfowler.com/eaaCatalog/money.html https://www.martinfowler.com/eaaCatalog/money.html [1] - https://github.com/topics/fowler-money-pattern https://github.com/topics/fowler-money-pattern
- war1025 7y agoOne thing I've always wondered is who the heck is Martin Fowler and why does he have such a following?
- jefft255 7y agoHe wrote influential software engineering textbooks on patterns, agile development, refactoring and the like. I'm not sure how relevant he is today, but in the 90s the ideas he presented were pretty groundbreaking.
- exdsq 7y agoI recommend his blog. He was, and I think might still be, ahead of the curve regarding software engineering patterns etc. There was an article shared on HN which hit #1 from a startups tech blog talking about their brand new architecture and someone pointed out MF wrote about an identical architecture pattern in 2004 or something.
- gnaritas 7y ago> One thing I've always wondered is who the heck is Martin Fowler and why does he have such a following? You don't know the history of your craft; study more, look into where agile came from, the extreme programming movement, and who was involved and how refactoring became a thing. Names like Ward Cunningham, Kent Beck, Martin Fowler, Ron Jeffries, Rob Martin, and Dave Thomas should be familiar to anyone who knows their craft.
- wruza 7y ago
- EastSmith 7y agoWork with cents everywhere and format the output in the UI. Done.
- moftz 7y agoUnless I have to actually work with floats, I try to only do everything with int math to keep things sane. Not to mention, that it usually makes calculations faster.
- Aardwolf 7y agoDecimal floating point exists (https://en.wikipedia.org/wiki/Decimal64_floating-point_format https://en.wikipedia.org/wiki/Decimal64_floating-point_forma...), is that considered useable for financial data, or does it turn out in practice to also suffer from the same issues? Since, e.g., dividing through 3 will not be exact here either of course.
- HideousKojima 7y agoIt doesn't have the same issues (at least from my experience in C#) but it runs quite a bit slower than normal floats and doubles: http://csharphelper.com/blog/2017/05/compare-the-performance-of-the-float-double-and-decimal-data-types-in-c/ http://csharphelper.com/blog/2017/05/compare-the-performance...
- throwaway744678 7y agoNot that simple, though. That's the "division and rounding" method of the article: share a $.99 fee equally between two parties. That's either 49 cents each or 50 cents each if you're not careful. In both cases, where is the extra cent?
- ars 7y agoIf you use floats and rounded that one cent would disappear with them as well. 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.
- Scirra_Tom 7y agoI'm currently rewriting our store implementation, and it's complicated. One example is that some currencies have 100 subunits (eg USD), some have 10 subunits (EG TWD/MOP) and some have no subunits (eg JPY). This is made slightly more difficult in that Stripe/Paypal will treat subunits differently, for example Paypal might treat JPY as having 100 subunits and Stripe will treat it with 0 subunits. You want to be really careful here not to charge 10x/100x more/less to the end customer than you imagine. Formatting currencies is also something you need to do carefully as dots and commas mean completely different things in various countries and can lead to unexpected charges for customers. Don't get me started on VAT :P All worth it in the end though, I enjoy writing this sort of thing and I do see long term benefit to our business that we do it all in-house. I'd also recommend future proofing implementations to use longs instead of ints for storing money values. Back when Bitcoin was less valuable we allowed BTC as a currency where 1 subunit = 1 satoshi (100,000,000 satoshis in a bitcoin). It was possible to overflow an int this way. You also can't really write payment systems with the speed you might write other code in your startup - errors and mistakes here could have real impact on peoples lives. For example, loops that create charges that don't end (possible with both Stripe AND Paypal). Loops can manifest themselves as lock collisions as well. Basically, be careful and if speed is important you probably want to outsource it at the expense of some control.
- dmos62 7y agoWhat's the long term benefit?
- Scirra_Tom 7y agoLots of third party implementations will take a few % off your bottom line. That's the main one. There's a couple of other points here as well: - If you do enough volume, you're able to negotiate the fee with the payment processor, we've done this successfully. If you outsource, that negotiation is the third parties margin. - I'm not sure if this is true for third parties, but if they wrap up all your payouts in your native currency (EG GBP£), you lose money on forex. We try our best to have all our EUR and USD sales land in their respective currency accounts, then convert them with low cost services such as transferwise which has saved us a lot of money in the long run. Saving on your forex and having no third party processor fees, I think in some instances you could be saving in the region of £40,000 to £80,000 per £1mm of sales. We also couldn't find a third party implementation that would allow us to bill for our product, varied by country, amount, currency AND billing frequency. This fine tuned control can have real benefit, for example we noticed in Ukraine we had high traffic but no sales - by introducing a low monthly UAH billing option we started to grow sales in this region. Other third party implementations won't handle VAT properly or comprehensively which can create more of an accounting burden, or round VAT transactions unfavourably (size of that benefit is irrelevant to very small obviously depending on volume of transactions!) This gap is being slowly filled from what I've been seeing though. Using a third party solution for SaaS will always have some level of disconnect, it just feels nicer for the customer when it's all integrated seamlessly. One example being you have full control over branding and distribution of invoices/receipts etc.
- kens 7y agoOne of the reasons people used COBOL is that it handled currency better. You'd say "ACCOUNT-BALANCE PIC S999999V99" to specify the account balance was signed with 8 digits, and an implied decimal point (V) before the last two. The math was done with integers so floating point inaccuracy wasn't a problem. An unrelated issue with money handling: I once got a credit card charge for $NaN (not a number). Fortunately it was resolved without me needing to write a check for $0/0. Photo of the receipt at http://www.righto.com/2008/05/importance-of-software-testing.html http://www.righto.com/2008/05/importance-of-software-testing...
- gmfawcett 7y agoObligatory reference to the Moonpig billing system, and Mark Dominus' excellent deep-dive article on its design: > Sometimes I see other people fuck up a project over and over, and I say “I could do that better”, and then I get a chance to try, and I discover it was a lot harder than I thought, I realize that those people who tried before are not as stupid as as I believed. That did not happen this time. Moonpig is a really good billing system. It is not that hard to get right. Those other guys really were as stupid as I thought they were. https://blog.plover.com/prog/Moonpig.html https://blog.plover.com/prog/Moonpig.html
- superzamp 7y agoThat is incredibly interesting, thank you for linking it!
- masukomi 7y agobecause not everyone knows. Most lisps can handle fractional numbers as actual fractional numbers without the BS floating point rounding. 1/3 of 100 For example, is 33.333 repeating. Most languages and databases will round this off at some point. Most lisps are perfectly happy with the idea of fractions and thus circumvent this problem entirely, at least, until you need to interface with some other brain damaged system or need to give someone physical cash money. If you're dealing with money, you should seriously consider using a lisp.
- deleted 7y ago[deleted]
- deleted 7y ago[deleted]
- antonyme 7y agoHow can you write an article about this without mentioning that you should use a Decimal type for working with currency calculations? - https://dzone.com/articles/never-use-float-and-double-for-monetary-calculatio https://dzone.com/articles/never-use-float-and-double-for-mo... - https://husobee.github.io/money/float/2016/09/23/never-use-floats-for-currency.html https://husobee.github.io/money/float/2016/09/23/never-use-f... - https://husobee.github.io/money/float/2016/09/23/never-use-floats-for-currency.html https://husobee.github.io/money/float/2016/09/23/never-use-f...
- aloukissas 7y agoReminded me of this: https://twitter.com/billkarwin/status/347561901460447232 https://twitter.com/billkarwin/status/347561901460447232
- anonsivalley652 7y agoThe easy way would've been to output a fixed fraction precision rather than a floating point number directly, but that doesn't mean the underlying data or checked algebraic operations are any good. There are several things that could've prevented this, but apparently they become forgotten every few decades or so. Here's one that could help: A fixed-point math library using a 64-bit word format like Q1:49:14. That would be a signed, 49-bit whole number with a 14-bit fraction (4 digits for nominal storage / 2 digits for settlement rounded in-place). With a ~$562 trillion limit, it would have an effective range of -562,949,953,421,311.9999 to +562,949,953,421,311.9999. This would work for almost all banking operations but could break down in hyperinflation and the distant future, where reserve and government banking would likely hit "Y2K" issues with it first. Considering 128-bit types exist (although at double the storage cost), it might be worth creating a Q:1:60+:14 format with built-in Hamming code (or similar) ECC for protection beyond that which exists at the hardware level (RAM, possibly CPU caches & IO backplane, probably storage.. but ECC and integrity isn't universal nor always possible).