0

I have a function to calculate the percentage of transaction fees. To test it, I wrote the function below to ensure that the rate calculations are accurate. However, the calculated rate (1 - part/whole) * 100 is off by a tiny margin to the actual rate passed to the function.

Is there a way to correct the error and ensure that rate === calculated rate?

P.S: Rounding to a fixed number of decimal places would not fix the problem as the rate can have a varying number of decimal places.

const getAmount = (amount, rate) => {
    const fee = amount * (rate / 100);
    const amountAfterFee = amount - fee;
    const calculatedRate = (1 - amountAfterFee / amount) * 100;
    return { amount, fee, amountAfterFee, rate, calculatedRate };
};

console.log(getAmount(3000, 5)); 
// { amount: 3000, fee: 150, amountAfterFee: 2850, rate: 5, calculatedRate: 5.000000000000004 }

console.log(getAmount(3000, 6.0)); 
// { amount: 3000, fee: 180, amountAfterFee: 2820, rate: 6, calculatedRate: 6.000000000000005 }

console.log(getAmount(3000, 7.3)); 
// { amount: 3000, fee: 219, amountAfterFee: 2781, rate: 7.3, calculatedRate: 7.299999999999995 }

console.log(getAmount(3000, 8.12345)); 
// { amount: 3000, fee: 243.7035, amountAfterFee: 2756.2965, rate: 8.12345, calculatedRate: 8.123449999999998 }
Kingsley CA
  • 10,685
  • 10
  • 26
  • 39
  • 2
    This looks like a classic floating point error to me. When precision is important, you shouldn't use floating points. You can run your calculations as integers and add the decimal later--for example 100 could equate to 1.00. If your numbers are going to be reallllly big or you need a lot of precision, to the point where even longs won't cut it (the maximum value of an unsigned long is `18446744073709551615`), then you'll need a library that can handle big integers. – Liftoff Sep 14 '21 at 00:13

1 Answers1

2

You are using floats to perform calculations. Due to their internal representation, there can be precision issues, such as the one you are facing. See the link for more details.

In summary, as you seem to be handling currency calculations, your best option is to use, instead of float, a specific datatype such as currency.js:

const m = (amount) => currency(amount, { precision: 10 })
const getAmount = (amount, rate) => {
    const fee = m(amount).multiply(m(rate).divide(100));
    const amountAfterFee = m(amount).subtract(fee);
    const calculatedRate = m(1).subtract(amountAfterFee.divide(amount)).multiply(100);
    return { amount, fee, amountAfterFee, rate, calculatedRate };
};

console.log(getAmount(3000, "5")); 
// { amount: 3000, fee: 150, amountAfterFee: 2850, rate: 5, calculatedRate: 5.000000000000004 }

console.log(getAmount(3000, "6.0")); 
// { amount: 3000, fee: 180, amountAfterFee: 2820, rate: 6, calculatedRate: 6.000000000000005 }

console.log(getAmount(3000, "7.3")); 
// { amount: 3000, fee: 219, amountAfterFee: 2781, rate: 7.3, calculatedRate: 7.299999999999995 }

console.log(getAmount(3000, "8.12345")); 
// { amount: 3000, fee: 243.7035, amountAfterFee: 2756.2965, rate: 8.12345, calculatedRate: 8.123449999999998 }
<script src="https://cdn.jsdelivr.net/npm/currency.js"></script>

Naturally, there are other options available, such as dinero.js. Which one is better for you depends on your specific case, as always.

acdcjunior
  • 132,397
  • 37
  • 331
  • 304