Good 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
This 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.
Your balance is $0.30000000004
51–60 of 69 posts
Re: Your balance is $0.30000000004
#52Work with cents everywhere and format the output in the UI. Done.
The smallest unit of US money is the mill or 1/1000 of a dollar. The smallest unit of US currency is the cent. You're supposed to work in mills and round to cents. 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 part…
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.
Re: Your balance is $0.30000000004
#53Earlier quoted context omitted.
browser 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?
> 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/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.
If you use numbers directly in exponential form in vanilla javascript, you already get better results.
Re: Your balance is $0.30000000004
#54Earlier quoted context omitted.
browser 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?
That was the main plot of Office Space. https://www.imdb.com/title/tt0151804/
Re: Your balance is $0.30000000004
#55Earlier quoted context omitted.
That was the main plot of Office Space. https://www.imdb.com/title/tt0151804/
And SuperMan 3 https://www.imdb.com/title/tt0086393/?ref_=fn_al_tt_1
Re: Your balance is $0.30000000004
#56Most 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.
Re: Your balance is $0.30000000004
#57Re: Your balance is $0.30000000004
#58Re: Your balance is $0.30000000004
#59Earlier quoted context omitted.
One thing I've always wondered is who the heck is Martin Fowler and why does he have such a following?
> 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 th…
Re: Your balance is $0.30000000004
#60You 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