4

I'm a bit lost on what to do with the following calculation. The problem is that decimals are being round up/down, which obviously doesn't give me the result I desire.

int totalValue = 275000;
int percentage = 2;

for (uint i = 0; i < 30; i++) {
  totalValue += totalValue * percentage / 100;
}

With the above code, the result is:

498108

The expected result is:

498124.436

I know that normally you could simply use the following calculation to work with a predetermined amount of decimals like so:

value * 10**4 (4 decimals)

But I haven't gotten that to work in this case, often resulting in insanely high numbers due to the multiplication and division that "do not make sense" once I revert the above calculation using:

value / 10**4

Thank you very much for your time. Any help would be very much appreciated!

4

You need to multiply before calculations and divide after that:

int decimals = 4;
int totalValue = 275000;
int percentage = 2;

totalValue = totalValue * 10**decimals;
for (uint i = 0; i < 30; i++) {
  int AddValue = totalValue * percentage;
  AddValue = AddValue / 100;
  totalValue += AddValue;
}
totalValue = totalValue / 10**decimals;

EDIT: fixed line 5 and the percentage calculation EDIT2: Actually you don't need to multiply the percentage and will get better results - fixed

  • Thank you for your answer! I was working on a solution that was similar to this, but I can see where I went wrong in several places. On line 5 I think it's missing an assignment, right? Also I keep getting "275000" as output with this example, but I haven't figured out why – Nick May 20 '19 at 13:49
  • Wont the line "percentage = percentage / (100 * 10**decimals);" also get truncated to 0? – Nick May 20 '19 at 13:55
  • 1
    made several errors in a hurry ... should be fixed now – KNK May 20 '19 at 14:39
  • Awesome, that seems to work! I'll validate it properly as soon as possible. One more question though. This function will still return a rounded number, right? So would it be better to take out the last line and account for the fact that the last 4 digits are decimals wherever I'm calling the backend from? -or am I doing something wrong? – Nick May 20 '19 at 14:47
  • 1
    There are no floating point numbers in Solidity (yet), so the number as always rounded at the end, but yes you can return the number as is and divide in the client to get a floating point result – KNK May 20 '19 at 14:58
7

Generally speaking, in order to avoid data-loss (i.e., precision-loss) in an arithmetic computation, try to postpone every division as much as possible, without altering the expression.

More specifically with regards to your code, it appears that you are trying to compute the result of applying compound interest: 275000 * 1.02 ** 30.

You can do this very precisely in Solidity, by calculating (off-line) the exact value of 1.02 ** 30, and storing it scaled up to the level of precision that you desire.

Moreover, you won't even need those 30 iterations in order to achieve all that, so you can optimize your code here for both accuracy and performance.

For example:

uint256 INTEREST_N = 181136158410335375505681049921897;
uint256 INTEREST_D =  10000000000000000000000000000000;
uint256 totalValue =  275000 * INTEREST_N / INTEREST_D;

Of course, I'm aware of the fact that you most likely want the 275000 part as an input from the outside rather than a hard-coded value (in which case, you could just as well compute everything outside of the contract).

But assuming that the number of iterations and the compound interest percentage are both constant (30 and 2% in your case), you can do it using the numerator and denominator described above.

If either the number of iterations or the compound interest percentage is a variable, then instead of a single pair of numerator and denominator you can use an array of numerators and an array of denominators.

If both the number of iterations and the compound interest percentage are variables, then instead of a pair of arrays you can use a pair of tables.

  • I appreciate the answer and understand the gist of it, thank you! I'll come back to this solution once my understanding of the issue has improved. – Nick May 22 '19 at 7:37
  • 1
    @Nick: NP. In your coding example, you could have simply used a constant (or two constants), so I suppose that this example does not represent what you're actually trying to achieve. As explained in my answer, I tend to guess that your intention is for one or more of these constants values (275000, 30 and 2%) to be an input variable. If you provide a description of the actual purpose, then I might be able to guide you through and suggest a scheme which will allow you to realize it while optimizing for both accuracy and performance (i.e., gas cost). – goodvibration May 22 '19 at 7:42
  • That's awesome! In my case all three of those values are variables, so I figured I might as well send the result of 1.02**30 from the backend/caller. Also, the value of 275000 can contain decimals. At this point I'm not too sure how that will affect the calculation, but added "100000000000000000000000000000000 / (10**decimals);" to make the amount of decimals in the outcome variable. – Nick May 22 '19 at 8:01
  • To add a more complete description of the variables with examples: the initial value (275000.5115 , max 4 decimals), interest rate (2.2152%, max 4 decimals) and amount of years (30, always a round number). To make it easier I can make sure there's always 4 decimals by padding zeroes. – Nick May 22 '19 at 8:07
  • @Nick: Sorry, but in that case you can do the full calculation offline and just put the result hard-coded in your contract. There is no need for any code apart from that. – goodvibration May 22 '19 at 9:37
2

The reason you are getting the value 498108 is because Solidity truncates divided values.

This means that if you divide 3/2, the result will be 1, not 1.5. Similarly, if you divide 11/3, the result will be 3, not 3.666.

With this in mind, the reason you are getting that final result is because a few of the loop results are being truncated.

  • So you're saying that it's not possible in any way? I'm aware of what's happening (actually I thought it was rounding it down/up, but you're saying it's truncating. Thanks for letting me know!), but I don't know how else to approach this to get the result I want. – Nick May 20 '19 at 13:02
0

Solidity does not support fractions out of the box, so you need to either use some sort of library (I would recommend ABDK Libraries) implement fraction math yourself. The first thing to do is to decide what format of fraction numbers suits for your case. This may be simple fraction (a pair of two integers: numerator and denominator), fixed point number (basically a simple fraction with predefined denominator), or floating point number of some sort. For your particular case I would recommend using fixed point numbers, as the range you need is quite limited. For real production contract the best approach would probably be to use well tested library, but for educational purpose manual implementation of fixed point math could be more interesting. So, what you need to do is:

  1. Decide what predefined denominator to use. Value 10^18 is quite common choice here.
  2. Decide what Solidity type to use for numerator. Type uint is usually good unless you need to support negative numbers.
  3. Now you need to implement conversion of your inputs from plain integers to fixed point numbers, so for integer number X you need to find numerator Y such that simple fraction Y / 10^18 equals to X. The obvious solution is: Y = X * 10^18.
  4. Now you need to implement multiplication of fixed point numbers. So, for two simple fractions: X / 10^18 and Y / 10^18 you need to find Z such that simple fraction Z / 10^18 equals to (X / 10^18) * (Y / 10^18). Obviously, Z = X * Y / 10^18.
  5. Then you need to implement division of fixed point number by integer number. So, for simple fraction X / 10^18 and integer number Y you need to find Z such that simple fraction Z / 10^18 equals to (X / 10^18) / Y. Obviously, Z = X / Y.
  6. Then you need to implement adding two fixed point numbers. So, for two simple fractions: X / 10^18 and Y / 10^18 you need to find Z such that simple fraction Z / 10^18 equals to X / 10^18 + Y / 10^18. Obviously, Z = X + Y.
  7. And finally, you need to convert your fixed point result back into integer. So for simple fraction X / 10^18 you need to find integer Y such that Y approximates X / 10^18. Obviously, Y = X / 10^18.

Putting all this together we can do the following naïve implementation:

function intToFixed (uint x) internal pure returns (uint y) { y = x * 10**18; }
function mulFixed (uint x, uint y) internal pure returns (uint z) { z = x * y / 10**18; }
function divFixedInt (uint x, uint y) internal pure returns (uint z) { z = x / y; }
function addFixed (uint x, uint y) internal pure returns (uint z) { z = x + y; }
function fixedToInt (uint x) internal pure returns (uint y) { y = x / 10**18; }
function f (uint totalValue, uint percentage) public pure returns (uint result) {
  uint totalValueFixed = intToFixed (totalValue);
  uint percentageFixed = intToFixed (percentage);

  for (uint i = 0; i < 30; i++) {
    totalValueFixed =
      addFixed (
        totalValueFixed,
        mulFixed (
          totalValueFixed,
          divFixedInt (percentageFixed, 100)));
  }

  result = fixedToInt (totalValueFixed);
}

For your data this code gives me 498124.

While this naïve approach works fine for small numbers, such as in your task, the mulFixed function will not work with really large numbers, because multiplications in the following exception: x * y / 10**18 could overflow even when final result fits into uint data type. So, better implementation of this function will look like this:

function mulFixed (uint x, uint y) internal pure returns (uint z) {
  uint a = x / 10**18;
  uint b = x % 10**18;
  uint c = y / 10**18;
  uint d = y % 10**18;
  return a * c * 10**18 + a * d + b * c + b * d / 10**18;
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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