article · December 23, 2023 · 7 min read
FixedPointMathLib vs ABDKMath64x64 - What should you use?
An article about which library to pick for fractional arithmetic in Solidity.
FixedPointMathLib vs ABDKMath64x64 - What should you use?
In Solidity we don’t have Fixed Point Arithmetic out-of-the-box, hence we rely on various libraries to make it easier and safer for us. FixedPointMathLib and ABDKMath64x64 are some of the well known libraries that handle this.
Lets see which one should you be using and why? There can be a lot of factor to support the decision of which one to use, we will go through a couple of things that matter and see which one has to be used based on the scenario that a smart contract developer has.
Differences
Precision and Range:
- ABDKMath64x64: Operates with a 64.64 fixed point math library, meaning it uses 64 bits for the integer part and 64 bits for the fractional part. This allows for high precision and a wide range of numbers, but it can be more computationally intensive.
- FixedPointMathLib: This library uses a different approach and have a different precision range with WAD factor which is
10**18.
Performance:
- ABDKMath64x64: Known for high precision, but this can come at the cost of gas (when used in Ethereum smart contracts), as operations are more complex and you might have to keep converting the numbers from
intto64x64format. - FixedPointMathLib: Less gas-intensive for certain operations as it relies on the Yul to make use of direct math functions mostly.
- ABDKMath64x64: Known for high precision, but this can come at the cost of gas (when used in Ethereum smart contracts), as operations are more complex and you might have to keep converting the numbers from
Use Cases:
- ABDKMath64x64: Its high precision makes it suitable for financial applications, complex calculations, and anywhere that a very small margin of error is critical.
- FixedPointMathLib: Good to use when we have simple calculations that can be done with in 18 decimal point precision. Most of the transactions of ERC20 and user facing data will be similar to this. So better for naive usage.
Code in action
1// SPDX-License-Identifier: MIT2pragma solidity >=0.8.0;34import "https://github.com/Vectorized/solady/blob/main/src/utils/FixedPointMathLib.sol";56import "https://github.com/abdk-consulting/abdk-libraries-solidity/blob/master/ABDKMath64x64.sol";78using FixedPointMathLib for uint256;9using FixedPointMathLib for int256;1011using ABDKMath64x64 for int128;1213contract Math {14 function mul0(uint256 a, uint256 b) public pure returns (uint256 c) {15 c = a * b;16 }1718 function mul1(int128 a, int128 b) public pure returns (int128 c) {19 c = a.mul(b);20 }2122 function mul2(uint256 a, uint256 b) public pure returns (uint256 c) {23 int128 aFixed = ABDKMath64x64.fromUInt(a);24 int128 bFixed = ABDKMath64x64.fromUInt(b);2526 // Perform the calculation using ABDKMath64x6427 int128 rawResult = aFixed.mul(bFixed);2829 // Convert the result back to uint for the return value30 c = ABDKMath64x64.toUInt(rawResult);31 }3233 function mul3(uint256 a, uint256 b) public pure returns (uint256 c) {34 c = a.mulWad(b);35 }3637 function pow0(int256 a, int256 b) public pure returns (int256 c) {38 c = a.powWad(b);39 }4041 function pow1(int128 a, uint256 b) internal pure returns (int128 c) {42 c = a.pow(b);43 }44}
This smart contract tries to make use of both the libraries to do some basic multiplication and then calculate power.
Now we will try to execute each of those and see how each performs. We only care about inputs, outputs and gas here. So rest of logs are omitted here.
mul0
1to: Math.mul0(uint256,uint256)23execution cost41049 gas (Cost only applies when called by a contract)[]56decoded input7{ "uint256 a": "120", "uint256 b": "130" }[]89decoded output10{ "0": "uint256: c 15600" }[]
This is direct multiplication in solidity without any help of libraries. Simple to understand inputs and output.
mul1
1to23Math.mul1(int128,int128) 0x9D7f74d0C41E726EC95884E0e97Fa6129e3b5E99[]45execution cost61058 gas (Cost only applies when called by a contract)[]78decoded input9{ "int128 a": "120", "int128 b": "130" }[]1011decoded output12{ "0": "int128: c 0" }[]
The output here is 0. Any idea about why is that 0?
It is because the ABDKMath64x64 library expects the number to be in the form of 64.64 bit representation. That means we need to convert the number to binary and then add 64 bit of binary 0 to make fractional part 0 for that number. Lets do that for 120 and 130
| decimal | binary (without 64 zeros for fractional part) | decimal for 64.64 |
|----------|---------|------------------|
|120 | 1111000 | 2213609288845146193920 |
|130 | 10000010 | 2398076729582241710080 |
1to23Math.mul1(int128,int128) 0x9D7f74d0C41E726EC95884E0e97Fa6129e3b5E99[]45execution cost61058 gas (Cost only applies when called by a contract)[]78decoded input9{ "int128 a": "2213609288845146193920", "int128 b": "2398076729582241710080" }[]1011decoded output12{ "0": "int128: c 287769207549869005209600" }[]
287769207549869005209600 = 111100111100000000000000000000000000000000000000000000000000000000000000000000 = 15600
mul2
1Math.mul2(uint256,uint256) 0x9D7f74d0C41E726EC95884E0e97Fa6129e3b5E99[]23execution cost41351 gas (Cost only applies when called by a contract)[]56decoded input7{ "uint256 a": "120", "uint256 b": "130" }[]89decoded output10{ "0": "uint256: c 15600" }[]
This is using ABDKMath64x64, but the catch here is we used extra helpers to convert instead of doing it all by ourselves like we did before. Hence extra gas cost.
mul3
1to23Math.mul3(uint256,uint256) 0x9D7f74d0C41E726EC95884E0e97Fa6129e3b5E99[]45execution cost6877 gas (Cost only applies when called by a contract)[]78decoded input9{ "uint256 a": "128", "uint256 b": "128" }[]1011decoded output12{ "0": "uint256: c 0" }[]
The same again, not the results we wanted to see. FixedPointMathLib expects a number with 18 decimal places i.e., we need to append 18 zeros after the number
1to23Math.mul3(uint256,uint256) 0x9D7f74d0C41E726EC95884E0e97Fa6129e3b5E99[]45execution cost6877 gas (Cost only applies when called by a contract)[]789decoded input10{ "uint256 a": "120000000000000000000", "uint256 b": "130000000000000000000" }[]1112decoded output13{ "0": "uint256: c 15600000000000000000000" }[]
Now we get the correct output but with 18 zeros appended to it.
pow0
1to23Math.pow0(int256,int256) 0x9D7f74d0C41E726EC95884E0e97Fa6129e3b5E99[]45execution cost62723 gas (Cost only applies when called by a contract)[]78decoded input9{ "int256 a": "2000000000000000000", "int256 b": "10000000000000000000" }[]1011decoded output12{ "0": "int256: c 1023999999999999995727" }[]
Uses the FixedPointMathLib. I would expect 2 ** 10 to be 1024. But the value here is 1023.999.....5727 which is not correct but close when we round up.
pow1
Users the ABDKMath64x64. For conversion
2 - 10 in binary and when converted to int with 64 bit decimal places, it is 18446744073709551616
10 - we give this directly as the power is expected in int128 but not 64.64
1to23Math.pow1(int128,uint256) 0xddaAd340b0f1Ef65169Ae5E41A8b10776a75482d[](https://remix.ethereum.org/#)45execution cost672302 gas (Cost only applies when called by a contract)[](https://remix.ethereum.org/#)89input10110x9ea...0000a[](https://remix.ethereum.org/#)1213decoded input1415{ "int128 a": "36893488147419103232", "uint256 b": "10" }[](https://remix.ethereum.org/#)1617decoded output1819{ "0": "int128: c 18889465931478580854784" }[](https://remix.ethereum.org/#)
The output when converted to binary will give us 10000000000+64 zeros. Those 64 zeros can be omitted to get the integral part which then converts to 1024, the exact output for 2 ** 10
Gas costs
| ABDKMath64x64 | FixedPointMathLib | |
|---|---|---|
| mul | 1058 | 877 |
| pow | 2302 | 2723 |
If we include gas to convert for ABDKMath64x64 it would add around 300
Conclusion
ABDKMath64x64 is gas efficient for complex calculations and very precise and accurate for smaller integral numbers with large fractional part. FixedPointMathLib is good with large numbers and has less precision for more complex calculations, but highly gas efficient for basic math.
So, if you are building something that include complex formulae like bonding curve, exponential farming or anything that requires high precision and complex calculations one should go for ABDKMath64x64, else if you require basic math with low precision which is mostly used in transfer, ERC20 related, more user facing applications and functions one should go for FixedPointMathLib, as for user facing you don’t have to put in extra effort to convert it to 64.64format when compared to WAD.
