macor161 / ts-money

Typescript port of js-money. Implementation of the Money value object.
MIT License
47 stars 9 forks source link

Money.fromDecimal is subject to floating point calculation errors #1

Closed anujbiyani closed 6 years ago

anujbiyani commented 6 years ago

These lines are subject to floating point errors:

let precisionMultiplier = Math.pow(10, currency.decimal_digits)
let resultAmount = amount * precisionMultiplier

For example: image

The result of this calculation is assumed to be an integer:

if (!isInt(amount))
    throw new TypeError('Amount must be an integer')

but because of floating point calculation inaccuracies that's not always the case.

It's easy to workaround this issue by passing a rounder to Math.fromDecimal, but I feel like this issue is better fixed by ts-money itself, e.g. by using a default rounder.

Would you be open to a PR to address this?

macor161 commented 6 years ago

Hi @anujbiyani Thank you very much for submitting this issue. I will create a patch this week.

anujbiyani commented 6 years ago

In case it helps, here's the rounder function I'm using:

export function roundTo(n: number, digits: number = 0): number {
  let negative = false;

  if (n < 0) {
    negative = true;
    n = n * -1;
  }

  const multiplicator = Math.pow(10, digits);

  n = parseFloat((n * multiplicator).toFixed(11));
  n = +(Math.round(n) / multiplicator).toFixed(digits);

  if (negative) {
    n = +(n * -1).toFixed(digits);
  }

  return n;
}

It's a Typescript version of https://stackoverflow.com/questions/15762768/javascript-math-round-to-two-decimal-places/15762794#15762794

macor161 commented 6 years ago

Terribly sorry for the delay, I've been really busy lately. An update will be plublished by monday. Also, a new version of the library using native BigInt is currently in the making.

anujbiyani commented 6 years ago

No worries, thanks for the update! Let me know if I can help.

macor161 commented 6 years ago

@anujbiyani The issue should be fixed now. Basically, we now always use the rounder function in fromDecimal. If no rounder is provided, Math.round is used by default.

Here is the commit

Let me know if you think another approach would be better suited to fix this problem.

Thanks again for signaling the issue, really appreciated! :)

anujbiyani commented 6 years ago

Looks great! Thank you.