Home

Awesome

PRBMath GitHub Actions Foundry License: MIT

Solidity 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 format is due to the integer part having up to 59/60 decimals and the fractional part having up to 18 decimals. The numbers are bound by the minimum and the maximum values permitted by the Solidity types int256 and uint256.

I created this because I wanted a fixed-point math library that is at the same time intuitive, efficient and safe. I looked at ABDKMath64x64, which is fast, but it uses binary numbers which are counter-intuitive and non-familiar to humans. Then, I looked at Fixidity, which operates with denary numbers and has wide precision, but is slow and susceptible to phantom overflow. Finally, I looked at Solmate, which checks all the boxes mentioned thus far, but it doesn't offer type safety.

Install

Node.js

This is the recommended approach.

Install PRBMath using your favorite package manager, e.g., with Bun:

bun add @prb/math

Then, if you are using Foundry, you need to add this to your remappings.txt file:

@prb/math/=node_modules/@prb/math/

Git Submodules

This installation method is not recommended, but it is available for those who prefer it.

First, install the submodule using Forge:

forge install --no-commit PaulRBerg/prb-math@release-v4

Your .gitmodules file should now contain the following entry:

[submodule "lib/prb-math"]
  branch = "release-v4"
  path = "lib/prb-math"
  url = "https://github.com/PaulRBerg/prb-math"

Finally, add this to your remappings.txt file:

@prb/math/=lib/prb-math/

Usage

There are two user-defined value types:

  1. SD59x18 (signed)
  2. UD60x18 (unsigned)

If you don't know what a user-defined value type is, check out this blog post.

If you don't need negative numbers, there's no point in using the signed flavor SD59x18. The unsigned flavor UD60x18 is more gas efficient.

Note that PRBMath is not a library in the Solidity sense. It's just a collection of free functions.

Importing

It is recommended that you import PRBMath using specific symbols. Importing full files can result in Solidity complaining about duplicate definitions and static analyzers like Slither erroring, especially as repos grow and have more dependencies with overlapping names.

pragma solidity >=0.8.19;

import { SD59x18 } from "@prb/math/src/SD59x18.sol";
import { UD60x18 } from "@prb/math/src/UD60x18.sol";

Any function that is not available in the types directly has to be imported explicitly. Here's an example for the sd and the ud functions:

pragma solidity >=0.8.19;

import { SD59x18, sd } from "@prb/math/src/SD59x18.sol";
import { UD60x18, ud } from "@prb/math/src/UD60x18.sol";

Note that PRBMath can only be used in Solidity v0.8.19 and above.

SD59x18

// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.19;

import { SD59x18, sd } from "@prb/math/src/SD59x18.sol";

contract SignedConsumer {
  /// @notice Calculates 5% of the given signed number.
  /// @dev Try this with x = 400e18.
  function signedPercentage(SD59x18 x) external pure returns (SD59x18 result) {
    SD59x18 fivePercent = sd(0.05e18);
    result = x.mul(fivePercent);
  }

  /// @notice Calculates the binary logarithm of the given signed number.
  /// @dev Try this with x = 128e18.
  function signedLog2(SD59x18 x) external pure returns (SD59x18 result) {
    result = x.log2();
  }
}

UD60x18

// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.19;

import { UD60x18, ud } from "@prb/math/src/UD60x18.sol";

contract UnsignedConsumer {
  /// @notice Calculates 5% of the given signed number.
  /// @dev Try this with x = 400e18.
  function unsignedPercentage(UD60x18 x) external pure returns (UD60x18 result) {
    UD60x18 fivePercent = ud(0.05e18);
    result = x.mul(fivePercent);
  }

  /// @notice Calculates the binary logarithm of the given signed number.
  /// @dev Try this with x = 128e18.
  function unsignedLog2(UD60x18 x) external pure returns (UD60x18 result) {
    result = x.log2();
  }
}

Features

Because there's significant overlap between the features available in SD59x18 and UD60x18, there is only one table per section. If in doubt, refer to the source code, which is well-documented with NatSpec comments.

Mathematical Functions

NameOperatorDescription
absN/AAbsolute value
avgN/AArithmetic average
ceilN/ASmallest whole number greater than or equal to x
div/Fixed-point division
expN/ANatural exponential e^x
exp2N/ABinary exponential 2^x
floorN/AGreatest whole number less than or equal to x
fracN/AFractional part
gmN/AGeometric mean
invN/AInverse 1÷x
lnN/ANatural logarithm ln(x)
log10N/ACommon logarithm log10(x)
log2N/ABinary logarithm log2(x)
mul*Fixed-point multiplication
powN/APower function x^y
powuN/APower function x^y with y simple integer
sqrtN/ASquare root

Adjacent Value Types

PRBMath provides adjacent value types that serve as abstractions over other vanilla types:

Value TypeUnderlying Type
SD1x18int64
SD21x18int128
UD2x18uint64
UD21x18uint128

These are useful if you want to save gas by using a lower bit width integer, e.g., in a struct.

Note that these types don't have any mathematical functionality. To do math with them, you will have to unwrap them into a simple integer and then to the core types SD59x18 and UD60x18.

Casting Functions

All PRBMath types have casting functions to and from all other types, including a few basic types like uint128 and uint40.

NameDescription
intoSD1x18Casts a number to SD1x18
intoSD59x18Casts a number to SD59x18
intoUD2x18Casts a number to UD2x18
intoUD60x18Casts a number to UD60x18
intoUint256Casts a number to uint256
intoUint128Casts a number to uint128
intoUint40Casts a number to uint40
sd1x18Alias for SD1x18.wrap
sd59x18Alias for SD59x18.wrap
ud2x18Alias for UD2x18.wrap
ud60x18Alias for UD60x18.wrap

Conversion Functions

The difference between "conversion" and "casting" is that conversion functions multiply or divide the inputs, whereas casting functions simply cast them.

NameDescription
convert(SD59x18)Converts an SD59x18 number to a simple integer by dividing it by 1e18
convert(UD60x18)Converts a UD60x18 number to a simple integer by dividing it by 1e18
convert(int256)Converts a simple integer to SD59x18 by multiplying it by 1e18
convert(uint256)Converts a simple integer to UD60x18 type by multiplying it by 1e18

Helper Functions

In addition to offering mathematical, casting, and conversion functions, PRBMath provides numerous helper functions for user-defined value types:

NameOperatorDescription
add+Checked addition
and&Logical AND
eq==Equality
gt>Greater than operator
gte>=Greater than or equal to
isZeroN/ACheck if a number is zero
lshiftN/ABitwise left shift
lt<Less than
lte<=Less than or equal to
mod%Modulo
neq!=Not equal operator
not~Negation operator
or|Logical OR
rshiftN/ABitwise right shift
sub-Checked subtraction
unary-Checked unary
uncheckedAddN/AUnchecked addition
uncheckedSubN/AUnchecked subtraction
xor^Exclusive or (XOR)

These helpers are designed to streamline basic operations such as addition and equality checks, eliminating the need to constantly unwrap and re-wrap variables. However, it is important to be aware that utilizing these functions may result in increased gas costs compared to unwrapping and directly using the vanilla types.

// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.19;

import { UD60x18, ud } from "@prb/math/src/UD60x18.sol";

function addRshiftEq() pure returns (bool result) {
  UD60x18 x = ud(1e18);
  UD60x18 y = ud(3e18);
  y = y.add(x); // also: y = y + x
  y = y.rshift(2);
  result = x.eq(y); // also: y == x
}

Assertions

PRBMath comes with typed assertions that you can use for writing tests with PRBTest, which is based on Foundry. This is useful if, for example, you would like to assert that two UD60x18 numbers are equal.

pragma solidity >=0.8.19;

import { UD60x18, ud } from "@prb/math/src/UD60x18.sol";
import { Assertions as PRBMathAssertions } from "@prb/math/test/Assertions.sol";
import { PRBTest } from "@prb/math/src/test/PRBTest.sol";

contract MyTest is PRBTest, PRBMathAssertions {
  function testAdd() external {
    UD60x18 x = ud(1e18);
    UD60x18 y = ud(2e18);
    UD60x18 z = ud(3e18);
    assertEq(x.add(y), z);
  }
}

Gas Efficiency

PRBMath is faster than ABDKMath for abs, exp, exp2, gm, inv, ln, log2, but it is slower than ABDKMath for avg, div, mul, powu and sqrt.

The main reason why PRBMath lags behind ABDKMath's mul and div functions is that it operates with 256-bit word sizes, and so it has to account for possible intermediary overflow. ABDKMath, on the other hand, operates with 128-bit word sizes.

Note: I did not find a good way to automatically generate gas reports for PRBMath. See the #134 discussion for more details about this issue.

PRBMath

Gas estimations based on the v2.0.1 and the v3.0.0 releases.

SD59x18MinMaxAvgUD60x18MinMaxAvg
abs687270n/an/an/an/a
avg95105100avg575757
ceil82117101ceil787878
div431483451div205205205
exp3827972263exp187427422244
exp26326782104exp2178426522156
floor82117101floor434343
frac232323frac232323
gm26892690gm26893691
inv404040inv404040
ln46373064724ln41969023814
log1010490744337log1050386954571
log237772414243log233068253426
mul455463459mul219275247
pow64113388518pow64106376635
powu293247455681powu83245355471
sqrt140839716sqrt114846710

ABDKMath64x64

Gas estimations based on the v3.0 release of ABDKMath. See my abdk-gas-estimations repo.

MethodMinMaxAvg
abs889290
avg414141
div168168168
exp7737802687
exp27736002746
gavg166875719
inv157157157
ln707471647126
log2697270627024
mul111111111
pow30347401792
sqrt129809699

Contributing

Feel free to dive in! Open an issue, start a discussion or submit a PR.

Pre Requisites

You will need the following software on your machine:

In addition, familiarity with Solidity is requisite.

Set Up

Clone this repository including submodules:

$ git clone --recurse-submodules -j8 git@github.com:PaulRBerg/prb-math.git

Then, inside the project's directory, run this to install the Node.js dependencies:

$ bun install

Now you can start making changes.

Syntax Highlighting

You will need the following VSCode extensions:

Security

The codebase has undergone audits by leading security experts from Cantina and Certora. For a comprehensive list of all audits conducted, see the SECURITY file.

Caveat Emptor

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.

Contact

If you discover any bugs or security issues, please report them via Telegram.

Acknowledgments

License

This project is licensed under MIT.