We’ve all learned about floating point numbers in school. As opposed to integers that have no decimal points.
One does not need to be a math whiz to know that the number five is an integer while 5.43 is a floating point number. Clearly, we’re all familiar with the decimal point that is a clear point of differentiation between the two.
Of course, it’s no surprise that floats are used in programming languages and are even classified as a distinct primitive data type because of how necessary it is to daily computation.
When you begin learning Python or Java, you will use the float data type. Particularly, when you carry out the division operation between two floating point numbers themselves.
In Python, even if you do not have to declare a floating point number, dividing two of these numbers can result in a number of the same data type, as shown below:
We can produce the same result in Java too, even if we have to explicitly declare the ‘float’ primitive data type for all variables used.
As one can tell, this kind of computation is necessary for a number of applications, and which is precisely why operations with floating point numbers are available as a feature in both these programming languages.
Quite differently, if you’ve been learning Solidity, you should have noticed that it does not include the floating point number data type.
Instead, if you’ve written any code in Solidity, you’d be using numbers of either the signed or unsigned integer type.
So, if you want to perform the same computation - five divided by two - as shown in the previous section, here’s how it will look, as part of a smart contract:
However, the result that you obtain will not be the same, since Solidity does not support the float data type. At least, not just yet.
Specifically, Solidity will round the result towards zero. Which, in this case, and as shown above, will result in a value of two. Think of this as the modulo operation on both numbers.
While in normal circumstances, this shouldn’t matter much, there are times when the result can lead to an error in computation. Which is why it is recommended to avoid or postpone the division operation as much as possible.
Even if the BODMAS rule in school has required all math students to perform the division computation before multiplication, this isn’t recommended if you have to perform integer division in Solidity.
Let’s find out why, with this simple example, that carries out multiplication and division with three numbers:
If you enter the numbers one, three and five when deploying the smart contract, you should get the same value for the getResult and getResult2 functions, right?
If you use a simple calculator, you should get a float value of 1.666, which translates to one in Solidity, thanks to the absence of the float values.
Unfortunately, this isn’t what happens, when you check the results of the getResult and getResult2 functions, as shown below:
If we perform the division first, we get a final result of zero. As opposed to the expected value of one, when you postpone that operation to the end in getResult function.
As you can tell, even if we’ve computed this value by anticipating the absence of float values, there’s still an error that crops up that can cause a loss in precision. Which, in turn, can translate to an economic loss that can be bypassed easily.
So, how do we prevent the integer division error? More importantly, how do we increase precision for our computation? Let’s find out the three most common ways to do this.
Given that there are a number of approaches to circumventing this error, let’s begin with the simplest fix, and look at a couple more before calling it a day.
Method #1: Use a multiplier
Now, along with placing the division operation last, one way of ensuring that you do not end up with errors or imprecise values is by using a multiplier. In the example below, we will use a multiplier of 100 along with the same three numbers used earlier.
Now, when you deploy the contract with the following code and call both functions, this is the output:
Since the desired output is 1.666 or 166/100, we can see that getResult2 value provides us with the necessary accuracy when the multiplier works in conjunction with the three numbers. Of course, if you do not use the multiplier as in the getResult function, you will obtain 1.
Where 0.666 is truncated from the result, as expected by default when you use Solidity. So, to retrieve this value, all you have to do is divide the result by the multiplier.
As you might know, Solidity moves towards zero when it comes to rounding off in the case of both signed and unsigned integers, so this fix works in the case of signed integers too, once you deploy and run the code below with the arguments minus one, three and five:
When it comes to the values generated by the functions for signed integers, here they are:
Clearly, we are able to maintain precision for signed integers as well, using a multiplier. Although, there’s a precise way to round off signed integers that we will look at next.
Method #2: Use floor division for signed integers
Now, if one looks at the number line below, the result of performing integer division between two unsigned integers is rounded closer to zero. As in the case of obtaining 1.666, Solidity rounds it off to 1, which is a smaller number.
However, when it comes to signed integers, a result of -1.6666 will be rounded off to -1, which is the larger of the two numbers. So, floor division must be applied here as opposed to rounded-to-zero division that is implemented in Solidity by default. For the sake of precision, of course.
If the float data type was available, the value of -1.666 would be computed. While Solidity would round this down to -1, applying floor division to signed integers will reduce it to -2.
When you call the getResult and getResult2 functions, we obtain the value as shown below for the arguments minus one, three & five:
As you can tell, getResult computes the value using the rounded-towards-zero approach while getResult2 rounds it off to the smallest integer here, by virtue of floor division.
Method #3: Use the ABDKMath64x64 library
Now, for the final method, we will use the ABDKMath64x64 library, which converts the result of the division operations into fixed point numbers.
Yet again, using this library is said to improve accuracy as opposed to the rounding towards zero method that is available by default in Solidity. In order to understand the output, let us compare the results of the add with the div function, as shown below:
When you add the arguments 1, 1 and 1 when deploying the smart contract, you obtain the values, as shown below:
It should come as no surprise that the add function returns an integer value of two, with the three arguments adding up to that much. As for the div function, an int 128 value is returned that represents a signed 64.64-bit fixed point number and that you can perform the usual number operations with.
Of course, the ABDKMath64x64 library isn’t the only one that can be used to improve accuracy apart from preventing the integer division error.
There are several others with a few examples being Fixidity, DSMath and the BANKEX libraries that use different number formats. Number formats that are different from the 64.64-bit fixed point number format used in the example above. So, while these libraries might seem useful to explore, please remember that their number formats will not work with any of the other libraries available.