currency.js icon indicating copy to clipboard operation
currency.js copied to clipboard

Fundamental error or misunderstanding of the multiplication/division functions

Open mittermayr opened this issue 1 year ago • 2 comments

My entire reason for switching to currency.js just seems to have gone up in smoke, am I fundamentally wrong or is this actually meant to be:

currency(60.0).divide(1.1).multiply(1.1).format() should be: 60.00

but actually will print: 60.01

I understand why it prints 60.01 (54.545454* gets rounded to 54.55), but I thought the entire reason for using currency.js (and its multiply/divide functions) was to not have this happen?

This of course works: currency(60.0).divide(10).multiply(10).format() prints: 60.00

I read in another issue/comment that this may be a precision issue. But whatever precision I pick, it causes various weird results:

currency(60.0, { precision: 36 }).divide(1.1).multiply(1.1).format() = 59.999999999999992894572642398998141289

Thank you, I'm sure I am misunderstanding something here.

mittermayr avatar Nov 19 '24 17:11 mittermayr

This is the expected outcome with that set of operations.

currency.js evaluates each operation individually and precision is set with each operation. You are correct in your understanding that the intermediate value after the division is 54.55 then when you multiply by 1.1, the remainder gets rounded half up to 60.01.

but I thought the entire reason for using currency.js (and its multiply/divide functions) was to not have this happen

The primary goal of currency.js was to work around floating point errors but precision can still pop up depending on your operations.

In this case, if you require a higher level of precision with multiple operations, it would be better to instantiate the currency object twice with your desired calculation precision and your output precision:

currency(currency(60, { precision: 4 }).divide(1.1).multiply(1.1), { precision: 2 }).format()
// results in "$60.00" with an internal value of 60.0001 with a higher level of internal precision

scurker avatar Nov 20 '24 15:11 scurker

Thanks for the quick and extensive response, @scurker — much appreciated. This indeed produces the correct output. I think your explanation will help others, too. This seems to be more due to a lack of me wrapping my head around where exactly the precision is lost and where currency.js has its defaults rather than a bug. I was just so perplexed that the first basic calculation I ran through it threw me off the horse.

Basically, the use-case that triggered my issue was pretty straight forward: a cashbox system that works on net prices. Now there is one dialog where the user can enter a gross price (60), which already includes 10% tax.

For the system to work with this internally (all prices are net prices), it needs to calculate the net, which it does by dividing 60 by 1.1 — resulting in 54.5454....

Assuming the original 60 can't be stored (like as a shadow gross value to keep at hand for future use), the system is now stuck with the unpleasant periodic number, as a net value.

So this goes to storage (in memory or in db) — and once it comes back from there, the gross price (display price) needs to be calculated again to show to the user — which displays 60.01 now. The classic mistake everyone runs into at least once when messing around with numbers. Unexplicable to the user, because they just entered 60.

So, everyone recommended to not bother with any numbers and just run it through currency.js to now have to worry about it. And once I ran my super basic example above, I was perplexed as to what exactly happened.

In any case, this is just to throw some search words on top of your explanation in hopes of people finding this and not having to code in circles an entire evening.

Thanks!

mittermayr avatar Nov 20 '24 16:11 mittermayr