8 ms·
I looked into using Dinero.js before but it was overkill for my use case of calculating cart totals in USD only. I went with Big.js because it was better suited
by cpburns2009 8y ago
I looked into using Dinero.js before but it was overkill for my use case of calculating cart totals in USD only. I went with Big.js because it was better suited for what I wanted: a small library for decimal arithmetic.
- tlb 8y agoCan you illustrate a case where you can't use regular FP arithmetic and round to 2 decimal places at the end?
- maxxxxx 8y agoYou can't model 1.0 correctly but it will be something like 1.000006667. If you add enough of these numbers you will be off.
- recursive 8y agoYour details are off. 1.0 is precisely representable in double precision floating point with no error.
- maxxxxx 8y agoSorry, picked the wrong number. But I think the general idea is correct.
- aaaaaaaaaab 8y agoIntegers can be represented exactly in IEEE754.
- maxxxxx 8y agoHow about 2.1? According to this https://www.h-schmidt.net/FloatConverter/IEEE754.html https://www.h-schmidt.net/FloatConverter/IEEE754.html 2.1 will be represented as 2.099999904632568359375
- noselasd 8y agowell, 2.1 isn't an integer.
- noselasd 8y agoNot all of them, e.g. 9007199254740993 can't be represented in a double precision format (what javascript uses) For single precision floats you start having issues representing integers as low as 16777217
- aaaaaaaaaab 8y agoSure, you can’t represent Graham’s number either... I know you knew what I meant.
- noselasd 8y agoNo not really. You have to define the bounds you were talking about. People believing that a float type can represent all integers that the int type in their language can represent is not exactly uncommon.
- ekke 8y ago(1.005).toFixed(2) // Expected rounding to "1.01" "1.00"
- deleted 8y ago[deleted]
- noselasd 8y agocases like that's everywhere. 2.875.toFixed(2) = "2.88" while 2.675.toFixed(2) = "2.67" 7205759403792794 + 1.1 = 7205759403792795 7205759403792794 + 1.9 = 7205759403792796
- athenot 8y agoBring up a Javascript interpreter and try this: $ node > 0.1 + 0.2 - 0.3 5.551115123125783e-17 Close enough for trivial cases but errors can accumulate. Ruby, Perl5, Python also fail this. Perl6 uses Rat numbers for things like this and doesn't have this problem.
- tlb 8y agoYou'd need to accumulate 10^14 such errors to be off by an entire penny, which doesn't seem relevant to a web shopping cart.
- mikekchar 8y agoAddition and subtraction are usually fine as you point out. Multiplication and division can cause problems depending on the values and the number of times you do it. If you are doing more complicated financial calculations, you could get in trouble more quickly. If you are careful and know exactly what you are doing, then you can certainly get away with normal FP for many applications. My experience has been, though, that many developers have a bit of a laissez-faire attitude towards these kinds of details (time is, of course, another area where precision is incredibly important in corner cases). Rather than expecting all future programmers to be as diligent as they need to be, it's probably easier to simply use tools that make it difficult to make mistakes. Every time I'm working on financial systems I build currency classes (usually I'm doing OOP). Usually people grumble and there will be people who ignore the currency classes. I've never had to wait long before someone trips over an edge cases and everybody understands the benefits.
- dragonwriter 8y ago> Ruby, Perl5, Python also fail this. Ruby and Python have arbitrary precision decimal and rational support in the standard library. While there are arguments tobe had over whether using binary floats by default for simple decimal literals is a good choice, that's a different issue than not having any other kind of numbers available by default.
- recursive 8y agoSure. Divide by two with any kind of predictability. for (let n = 0; n < 1; n += .01) { console.log(`n: ${n}, half: ${(n / 2).toFixed(2)}`); }
- PeterisP 8y agoSure, split an amount in 3 payments - take one dollar, put 0.333333 dollars once, 0.3333333 dollars the second time, and the remainder (1 - 0.33333333 - 0.33333333) as the outstanding balance doesn't match what you should have; you should have had 0.34 but you have 0.33 if you round only at the end. Do the same with a proper decimal datatype, and the same operations give the correct result - pay out 1/3=0.33; pay out 1/3=0.33; the last payment is 1-0.33-0.33 = 0.34 exactly. The same goes with all kinds of other issues e.g. totalling up tax or discount amounts (calculated as a percentage) where you'll get mismatches if rounding happens at the wrong time, etc. E.g. ten separate receipts each selling a 0.10 item with a 9% tax or discount will result in each receipt having 0.01 tax or discount; so the proper total in this case is 1.00 sales with 0.10 tax or discount; different than the case if you sell ten such items in a single transaction. If you do all the calculations in floats, you get this wrong.
- strainer 8y agoI think the advantage behind using something like Big.js is to future proof the code against hyper-inflation. Though a dedicated currency engine would better have the ability to set and conveniently update the currency denomination of stored values to cope with this. With float64's maximum 'safe' integer at almost ten thousand trillion, I believe this should be enough to compute most commercial transactions securely as long as the amounts base denomination is up to date (eg cents or dollars or megadollars). The quantization noise mentioned in this article is possible to round off when appropriate. function round_tail_digits(c){ return Math.round(c*1e15)/1e15 } function round_at_hundreds(c){ return Math.round(c*100)/100 }
- sk5t 8y agoThis approach, at least in languages like C and Java, would effectively smush the effective MAX_INT down to MAX_INT/1e15 or whatever. No bueno.
- strainer 8y agoIt doesn't because basically - the point floats. The max_int doesn't mean the largest whole number that can be represented, it effectively means how much resolution you have on the number line in every one of a very wide range of scales. These languages all give exactly the same standard IEEE754 float math results for basic operations.
- smallnamespace 8y ago> I think the advantage behind using something like Big.js is to future proof the code against hyper-inflation. This is probably over-engineering. Currencies that go through hyperinflation typically end up being officially re-denominated many times, there's no 'zero maintenance' path that a library will give you.