18

I'm trying to calculate a percentage of a number in solidity with the following code.

The percentage is technically basis points (10000ths, instead of 100ths) because I want decimal percentage values.

uint128 is enough for expressing the normal numbers, but to try to avoid precision errors, I cast the values to int256 for the calculation.

pragma solidity 0.4.24;
contract PercentageCalc {
  uint128 public bp = 185; // 1.85% in basis points (parts per 10,000)

  function calculatePercentage(
    uint128 theNumber
  )
  public
  view
  returns (uint128) {
    return uint128(int256(theNumber) / int256(10000) * int256(bp));
  }
}

The problem is if I input 111111111111111111 as theNumber, the output is 2055555555555535 instead of the desired output of 2055555555555555 (what I get by doing the calculation with for example BigNumber.js).

I'm guessing it is some precision error I have overlooked. How do it get the desired output of 2055555555555555?

1
  • 4
    MULTIPLY BEFORE YOU DIVIDE!!! (and make sure that the multiplication doesn't overflow of course). Aug 2, 2018 at 13:30

3 Answers 3

23

Do the multiplication first:

return theNumber * bp / 10000;

The problem with dividing first is that the result is an integer, so it chops off anything that drops behind the decimal point.

The thing to watch out for when multiplying first is integer overflow, so make sure your numbers are in an appropriate range where such multiplication won't exceed the maximum value of your data type.

5

While method, suggested by @user19510, works well in simple cases (probably including your case), it has one important drawback: the intermediary multiplication may overflow even when final result does fit into result type. For example, this method cannot be used to calculate 3% of 2^256 - 1, while both, 2^256 - 1 and 3% of it do fit into Solidity uint type. So as a general method I would recommend using the following function:

// Calculate x * y / scale rounding down.
function mulScale (uint x, uint y, uint128 scale)
internal pure returns (uint) {
  uint a = x / scale;
  uint b = x % scale;
  uint c = y / scale;
  uint d = y % scale;

  return a * c * scale + a * d + b * c + b * d / scale;
}

This implementation overflows ONLY when final result does not fit into uint type. Note, that scale is uint128 rather than uint. Of cause, in production code, all + and * operation should be done via SafeMath. I used plain operators here to make code easier to read.

The idea is that we to the following substitutions:

x = a * scale + b
y = c * scale + d

where b < scale and d < scale. Then:

x * y / scale =
(a * scale + b) * (c * scale + d) / scale =
(a * c * scale^2 + (a * d + b * c) * scale + b * d) / scale =
a * c * scale + a * d + b * c + b * d / scale

As long as scale fits into uint128 and both, b and d are less than scale, then b * d fits into uint256 so no intermediary overflow is possible.

If you need to round up, replace b * d / scale with (b * d + scale - 1) / scale. If you need to round to the closets integer, use (b * d + scale / 2) / scale.

2

Here is a drop in solution with information from other answers for reward percentage calculation.

 uint256 result = ((amount * interestRate * timeDiff) / lockPeriod) / 1e4;

where interestRate percentage is set like this.

uint256 public interestRate = 2000; //20 Percent

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.