Krushi Raj Tula / Writings / Fixed point vs abdk math /

Image from Google Images

FixedPointMathLib vs ABDKMath64x64 - What should you use?

December 23, 2023

7 mins.

Technology tags:
solidityweb3

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

  1. 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.
  2. 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 int to 64x64 format.
    • FixedPointMathLib: Less gas-intensive for certain operations as it relies on the Yul to make use of direct math functions mostly.
  3. 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: MIT
2pragma solidity >=0.8.0;
3
4import "https://github.com/Vectorized/solady/blob/main/src/utils/FixedPointMathLib.sol";
5
6import "https://github.com/abdk-consulting/abdk-libraries-solidity/blob/master/ABDKMath64x64.sol";
7
8using FixedPointMathLib for uint256;
9using FixedPointMathLib for int256;
10
11using ABDKMath64x64 for int128;
12
13contract Math {
14 function mul0(uint256 a, uint256 b) public pure returns (uint256 c) {
15 c = a * b;
16 }
17
18 function mul1(int128 a, int128 b) public pure returns (int128 c) {
19 c = a.mul(b);
20 }
21
22 function mul2(uint256 a, uint256 b) public pure returns (uint256 c) {
23 int128 aFixed = ABDKMath64x64.fromUInt(a);
24 int128 bFixed = ABDKMath64x64.fromUInt(b);
25
26 // Perform the calculation using ABDKMath64x64
27 int128 rawResult = aFixed.mul(bFixed);
28
29 // Convert the result back to uint for the return value
30 c = ABDKMath64x64.toUInt(rawResult);
31 }
32
33 function mul3(uint256 a, uint256 b) public pure returns (uint256 c) {
34 c = a.mulWad(b);
35 }
36
37 function pow0(int256 a, int256 b) public pure returns (int256 c) {
38 c = a.powWad(b);
39 }
40
41 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)
2
3execution cost
41049 gas (Cost only applies when called by a contract)[]
5
6decoded input
7{ "uint256 a": "120", "uint256 b": "130" }[]
8
9decoded output
10{ "0": "uint256: c 15600" }[]

This is direct multiplication in solidity without any help of libraries. Simple to understand inputs and output.

mul1

1to
2
3Math.mul1(int128,int128) 0x9D7f74d0C41E726EC95884E0e97Fa6129e3b5E99[]
4
5execution cost
61058 gas (Cost only applies when called by a contract)[]
7
8decoded input
9{ "int128 a": "120", "int128 b": "130" }[]
10
11decoded output
12{ "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 |

1to
2
3Math.mul1(int128,int128) 0x9D7f74d0C41E726EC95884E0e97Fa6129e3b5E99[]
4
5execution cost
61058 gas (Cost only applies when called by a contract)[]
7
8decoded input
9{ "int128 a": "2213609288845146193920", "int128 b": "2398076729582241710080" }[]
10
11decoded output
12{ "0": "int128: c 287769207549869005209600" }[]

287769207549869005209600 = 111100111100000000000000000000000000000000000000000000000000000000000000000000 = 15600

mul2

1Math.mul2(uint256,uint256) 0x9D7f74d0C41E726EC95884E0e97Fa6129e3b5E99[]
2
3execution cost
41351 gas (Cost only applies when called by a contract)[]
5
6decoded input
7{ "uint256 a": "120", "uint256 b": "130" }[]
8
9decoded output
10{ "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

1to
2
3Math.mul3(uint256,uint256) 0x9D7f74d0C41E726EC95884E0e97Fa6129e3b5E99[]
4
5execution cost
6877 gas (Cost only applies when called by a contract)[]
7
8decoded input
9{ "uint256 a": "128", "uint256 b": "128" }[]
10
11decoded output
12{ "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

1to
2
3Math.mul3(uint256,uint256) 0x9D7f74d0C41E726EC95884E0e97Fa6129e3b5E99[]
4
5execution cost
6877 gas (Cost only applies when called by a contract)[]
7
8
9decoded input
10{ "uint256 a": "120000000000000000000", "uint256 b": "130000000000000000000" }[]
11
12decoded output
13{ "0": "uint256: c 15600000000000000000000" }[]

Now we get the correct output but with 18 zeros appended to it.

pow0

1to
2
3Math.pow0(int256,int256) 0x9D7f74d0C41E726EC95884E0e97Fa6129e3b5E99[]
4
5execution cost
62723 gas (Cost only applies when called by a contract)[]
7
8decoded input
9{ "int256 a": "2000000000000000000", "int256 b": "10000000000000000000" }[]
10
11decoded output
12{ "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

1to
2
3Math.pow1(int128,uint256) 0xddaAd340b0f1Ef65169Ae5E41A8b10776a75482d[](https://remix.ethereum.org/#)
4
5execution cost
6
72302 gas (Cost only applies when called by a contract)[](https://remix.ethereum.org/#)
8
9input
10
110x9ea...0000a[](https://remix.ethereum.org/#)
12
13decoded input
14
15{ "int128 a": "36893488147419103232", "uint256 b": "10" }[](https://remix.ethereum.org/#)
16
17decoded output
18
19{ "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

ABDKMath64x64FixedPointMathLib
mul1058877
pow23022723

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.


Krushi Raj Tula

By Krushi Raj Tula A developer, geek, enthusiast, who loves to solve problems and fix things with technology. You can reach out to him on Twitter!