There has been a lot of progress since the beginning of Ethereum about best practices in Solidity. Unfortunately, I have the feeling that most of the knowledge is within the circle of experienced people and there aren’t that many online resources about it. That is why I would like to start this tutorial series called Solidity Design Patterns.

We are going to start it off by one straight-forward about math. I hope everybody is using SafeMath or something similar by this point in time. Nonetheless, there are still things to consider.

Do your multiplication before division!

This one is actually even true in JavaScript. To see why this is important, open up your browser console and type

console.log((30 * 100 * 13) / 13)
< 3000

Now, let’s do the division first. After all, abc/c == a/c * bc , right?

console.log((30 / 13) * 100 * 13)
< 2999.9999999999995

Now in the case of JavaScript, this is due to floating point errors. In Solidity we don’t have floating points, but instead we get rounding errors. With runtime uint256 values, (uint256(30) / 13) * 100 * 13 yields 2600: the first division truncates to 2. By contrast, a literal-only expression such as (30 / 13) * 100 * 13 is evaluated with rational precision at compile time and yields 3000.

By doing all our multiplications first, we mitigate rounding related issues as much as possible. The computations can be much more complex and forming them into a multiplication first formula can be challenging at times.

watch_out


Red “Watch out” stamp.

Not always sufficient!

Sometimes this is not good enough. For payouts, choose rounding deliberately and account for the remainder. The loss is not necessarily a single wei: later multiplications can magnify an earlier truncation, and repeated payouts can accumulate a difference. Check conservation and fairness requirements rather than assuming small-looking rounding is harmless.

This does not mean that it is always fine. Depending on your use case, you might want to favor an implementation using a numerator and denominator.

uint256 numerator = 30 * 100 * 13;
uint256 denominator = 13;

You can always store the number pair and do your computations according to proper math. In most cases, having a number rounded down will be fine though. Just know that this can happen and deal with it when you have to.

Solidity 0.8 update: Arithmetic overflow is checked by default outside unchecked blocks, so ordinary checked arithmetic no longer needs SafeMath. Multiplying first can still overflow even when the final quotient fits. For that case, consider OpenZeppelin Math.mulDiv and choose the rounding direction your accounting requires.