Smart contract library for advanced fixed-point math that operates with signed 59.18-decimal fixed-point and unsigned 60.18-decimal fixed-point numbers. The name of the number formats stems from the fact that there can be up to 59/60 digits in the integer part and up to 18 decimals in the fractional part. The numbers are bound by the minimum and the maximum values permitted by the Solidity types int256 and uint256.
- Designed for Solidity >=0.8.0
- Operates with signed and unsigned denary fixed-point numbers, with 18 trailing decimals
- Offers advanced math functions like logarithms, exponentials, powers and square roots
- Gas efficient, but still user-friendly
- Bakes in overflow-safe multiplication and division
- Well-documented via NatSpec comments
- Thoroughly tested with Hardhat and Waffle
I created this because I wanted a fixed-point math library that is at the same time practical, intuitive and efficient. I looked at ABDKMath64x64, which is fast, but I didn't like that it operates with binary numbers and it limits the precision to int128. I then looked at Fixidity, which operates with denary numbers and has wide precision, but is slow and susceptible to phantom overflow.
This is experimental software and is provided on an "as is" and "as available" basis. I do not give any warranties and will not be liable for any loss, direct or indirect through continued use of this codebase.
With yarn:
$ yarn add prb-math
Or npm:
npm install prb-math
PRBMath comes in four flavours: basic signed, typed signed, basic unsigned and typed unsigned.
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.0;
import "prb-math/contracts/PRBMathSD59x18.sol";
contract SignedConsumer {
using PRBMathSD59x18 for int256;
function signedLog2(int256 x) external pure returns (int256 result) {
result = x.log2();
}
/// @notice Calculates x*y÷1e18 while handling possible intermediary overflow.
/// @dev Try this with x = type(int256).max and y = 5e17.
function signedMul(int256 x, int256 y) external pure returns (int256 result) {
result = x.mul(y);
}
/// @dev Assuming that 1e18 = 100% and 1e16 = 1%.
function signedYield(int256 principal, int256 apr) external pure returns (int256 result) {
result = principal.mul(apr);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.0;
import "prb-math/contracts/PRBMathSD59x18Typed.sol";
contract SignedConsumerTyped {
using PRBMathSD59x18Typed for PRBMath.SD59x18;
function signedLog2(int256 x) external pure returns (int256 result) {
PRBMath.SD59x18 memory xsd = PRBMath.SD59x18({ value: x });
result = xsd.log2().value;
}
/// @notice Calculates x*y÷1e18 while handling possible intermediary overflow.
/// @dev Try this with x = type(int256).max and y = 5e17.
function signedMul(int256 x, int256 y) external pure returns (int256 result) {
PRBMath.SD59x18 memory xsd = PRBMath.SD59x18({ value: x });
PRBMath.SD59x18 memory ysd = PRBMath.SD59x18({ value: y });
result = xsd.mul(ysd).value;
}
/// @dev Assuming that 1e18 = 100% and 1e16 = 1%.
function signedYield(int256 principal, int256 apr) external pure returns (int256 result) {
PRBMath.SD59x18 memory principalSd = PRBMath.SD59x18({ value: principal });
PRBMath.SD59x18 memory aprSd = PRBMath.SD59x18({ value: apr });
result = principalSd.mul(aprSd).value;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.0;
import "prb-math/contracts/PRBMathUD60x18.sol";
contract UnsignedConsumer {
using PRBMathUD60x18 for uint256;
/// @dev Note that "x" must be greater than or equal to 1e18, lest the result would be negative, and negative
/// numbers are not supported by the unsigned 60.18-decimal fixed-point representation.
function unsignedLog2(uint256 x) external pure returns (uint256 result) {
result = x.log2();
}
/// @notice Calculates x*y÷1e18 while handling possible intermediary overflow.
/// @dev Try this with x = type(uint256).max and y = 5e17.
function unsignedMul(uint256 x, uint256 y) external pure returns (uint256 result) {
result = x.mul(y);
}
/// @dev Assuming that 1e18 = 100% and 1e16 = 1%.
function unsignedYield(uint256 principal, uint256 apr) external pure returns (uint256 result) {
result = principal.mul(apr);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.0;
import "prb-math/contracts/PRBMathUD60x18Typed.sol";
contract UnsignedConsumerTyped {
using PRBMathUD60x18Typed for PRBMath.UD60x18;
function unsignedLog2(uint256 x) external pure returns (uint256 result) {
PRBMath.UD60x18 memory xud = PRBMath.UD60x18({ value: x });
result = xud.log2().value;
}
/// @notice Calculates x*y÷1e18 while handling possible intermediary overflow.
/// @dev Try this with x = type(uint256).max and y = 5e17.
function unsignedMul(uint256 x, uint256 y) external pure returns (uint256 result) {
PRBMath.UD60x18 memory xud = PRBMath.UD60x18({ value: x });
PRBMath.UD60x18 memory yud = PRBMath.UD60x18({ value: y });
result = xud.mul(yud).value;
}
/// @dev Assuming that 1e18 = 100% and 1e16 = 1%.
function unsignedYield(uint256 principal, uint256 apr) external pure returns (uint256 result) {
PRBMath.UD60x18 memory principalUd = PRBMath.UD60x18({ value: principal });
PRBMath.UD60x18 memory aprUd = PRBMath.UD60x18({ value: apr });
result = principalUd.mul(aprUd).value;
}
}
The typeless PRBMath library is faster than ABDKMath for abs
, exp
, exp2
, gm
, inv
, ln
,
log2
. Conversely, it is slower than ABDKMath for avg
, div
, mul
, powu
and sqrt
. There are
two technical reasons why PRBMath lags behind ABDKMath's mul
and div
functions:
- PRBMath operates with 256-bit word sizes, so it has to account for possible intermediary overflow. ABDKMath operates with 128-bit word sizes.
- PRBMath rounds up instead of truncating in certain cases (see listing 6 and text above it in this article), which makes it slightly more precise than ABDKMath but comes at a gas cost.
Based on the latest version of the library.
SD59x18 | Min | Max | Avg | UD60x18 | Min | Max | Avg | |
---|---|---|---|---|---|---|---|---|
abs | 68 | 72 | 70 | n/a | n/a | n/a | n/a | |
avg | 57 | 57 | 57 | avg | 57 | 57 | 57 | |
ceil | 82 | 117 | 101 | ceil | 78 | 78 | 78 | |
div | 431 | 483 | 451 | div | 205 | 205 | 205 | |
exp | 38 | 2797 | 2263 | exp | 1874 | 2742 | 2244 | |
exp2 | 63 | 2678 | 2104 | exp2 | 1784 | 2652 | 2156 | |
floor | 82 | 117 | 101 | floor | 43 | 43 | 43 | |
frac | 23 | 23 | 23 | frac | 23 | 23 | 23 | |
fromInt | 83 | 83 | 83 | fromUint | 49 | 49 | 49 | |
gm | 26 | 892 | 690 | gm | 26 | 893 | 691 | |
inv | 40 | 40 | 40 | inv | 40 | 40 | 40 | |
ln | 463 | 7306 | 4724 | ln | 419 | 6902 | 3814 | |
log10 | 104 | 9074 | 4337 | log10 | 503 | 8695 | 4571 | |
log2 | 377 | 7241 | 4243 | log2 | 330 | 6825 | 3426 | |
mul | 455 | 463 | 459 | mul | 219 | 275 | 247 | |
pow | 64 | 11338 | 8518 | pow | 64 | 10637 | 6635 | |
powu | 293 | 24745 | 5681 | powu | 83 | 24535 | 5471 | |
sqrt | 140 | 839 | 716 | sqrt | 114 | 846 | 710 | |
toInt | 23 | 23 | 23 | toUint | 23 | 23 | 23 |
Based on the latest version of the library.
SD59x18 | Min | Max | Avg | UD60x18 | Min | Max | Avg | |
---|---|---|---|---|---|---|---|---|
abs | 128 | 132 | 130 | n/a | n/a | n/a | n/a | |
add | 221 | 221 | 221 | add | 97 | 97 | 97 | |
avg | 120 | 120 | 120 | avg | 120 | 120 | 120 | |
ceil | 95 | 166 | 141 | ceil | 132 | 132 | 132 | |
div | 524 | 582 | 546 | div | 259 | 259 | 259 | |
exp | 82 | 3076 | 2617 | exp | 2086 | 2954 | 2456 | |
exp2 | 110 | 2768 | 2233 | exp2 | 1840 | 2708 | 2212 | |
floor | 95 | 171 | 143 | floor | 97 | 97 | 97 | |
fromInt | 118 | 118 | 118 | fromUint | 84 | 84 | 84 | |
frac | 82 | 82 | 82 | frac | 77 | 77 | 77 | |
gm | 67 | 947 | 741 | gm | 67 | 948 | 743 | |
inv | 82 | 82 | 82 | inv | 82 | 82 | 82 | |
ln | 645 | 7503 | 5041 | ln | 626 | 7124 | 4029 | |
log10 | 182 | 9287 | 4337 | log10 | 2414 | 8912 | 7280 | |
log2 | 422 | 7285 | 4701 | log2 | 407 | 6910 | 4108 | |
mul | 538 | 546 | 542 | mul | 273 | 336 | 305 | |
pow | 115 | 11824 | 8471 | pow | 115 | 11129 | 7346 | |
powu | 479 | 25213 | 5931 | powu | 132 | 24426 | 4207 | |
sqrt | 195 | 918 | 798 | sqrt | 153 | 903 | 769 | |
sub | 218 | 218 | 218 | sub | 98 | 98 | 98 | |
toInt | 29 | 29 | 29 | toUint | 29 | 29 | 29 |
Based on v3.0 of the library. See abdk-gas-estimations.
Method | Min | Max | Avg |
---|---|---|---|
abs | 88 | 92 | 90 |
avg | 41 | 41 | 41 |
div | 168 | 168 | 168 |
exp | 77 | 3780 | 2687 |
exp2 | 77 | 3600 | 2746 |
gavg | 166 | 875 | 719 |
inv | 157 | 157 | 157 |
ln | 7074 | 7164 | 7126 |
log2 | 6972 | 7062 | 7024 |
mul | 111 | 111 | 111 |
pow | 303 | 4740 | 1792 |
sqrt | 129 | 809 | 699 |
Familiarity with Hardhat, Ethers, Waffle and TypeScript is requisite.
Before running any command, make sure to install dependencies:
$ yarn install
Compile the smart contracts with Hardhat:
$ yarn compile
Compile the smart contracts and generate TypeChain artifacts:
$ yarn typechain
Lint the Solidity code:
$ yarn lint:sol
Lint the TypeScript code:
$ yarn lint:ts
Run the Mocha tests:
$ yarn test
Generate the code coverage report:
$ yarn coverage
Delete the smart contract artifacts, the coverage reports and the Hardhat cache:
$ yarn clean
While I set a high bar for code quality and test coverage, you shouldn't assume that this library is completely safe to use. The contracts have not been audited by a security researcher. If you discover any security issues, please report them via Keybase.
I am grateful to:
- Mikhail Vladimirov for the insights he shared in his Math in Solidity series.
- Remco Bloemen for his work on overflow-safe multiplication and division and for responding to the questions I asked him while developing the library.
The library is released under the WTFPL License.