• Stars
    star
    805
  • Rank 54,436 (Top 2 %)
  • Language
    Solidity
  • License
    MIT License
  • Created about 3 years ago
  • Updated 7 days ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Solidity library for advanced fixed-point math

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.

  • Operates with signed and unsigned denary fixed-point numbers, with 18 trailing decimals
  • Offers advanced math functions like logarithms, exponentials, powers and square roots
  • Provides type safety via user-defined value types
  • Gas efficient, but still user-friendly
  • Ergonomic developer experience thanks to using free functions instead of libraries
  • Bakes in overflow-safe multiplication and division via mulDiv
  • Reverts with custom errors instead of reason strings
  • Well-documented with NatSpec comments
  • Built and tested with Foundry

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

Foundry

First, run the install step:

forge install PaulRBerg/prb-math@release-v4

Then, add this to your remappings.txt file:

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

Node.js

pnpm add @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/SD59x18.sol";
import { UD60x18 } from "@prb/math/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/SD59x18.sol";
import { UD60x18, ud } from "@prb/math/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/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/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

Name Operator Description
abs N/A Absolute value
avg N/A Arithmetic average
ceil N/A Smallest whole number greater than or equal to x
div / Fixed-point division
exp N/A Natural exponential e^x
exp2 N/A Binary exponential 2^x
floor N/A Greatest whole number less than or equal to x
frac N/A Fractional part
gm N/A Geometric mean
inv N/A Inverse 1Γ·x
ln N/A Natural logarithm ln(x)
log10 N/A Common logarithm log10(x)
log2 N/A Binary logarithm log2(x)
mul * Fixed-point multiplication
pow N/A Power function x^y
powu N/A Power function x^y with y simple integer
sqrt N/A Square root

Adjacent Value Types

PRBMath provides adjacent value types that serve as abstractions over other vanilla types such as int64. The types currently available are:

Value Type Underlying Type
SD1x18 int64
UD2x18 uint64

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.

Name Description
intoSD1x18 Casts a number to SD1x18
intoSD59x18 Casts a number to SD59x18
intoUD2x18 Casts a number to UD2x18
intoUD60x18 Casts a number to UD60x18
intoUint256 Casts a number to uint256
intoUint128 Casts a number to uint128
intoUint40 Casts a number to uint40
sd1x18 Alias for SD1x18.wrap
sd59x18 Alias for SD59x18.wrap
ud2x18 Alias for UD2x18.wrap
ud60x18 Alias 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.

Name Description
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:

Name Operator Description
add + Checked addition
and & Logical AND
eq == Equality
gt > Greater than operator
gte >= Greater than or equal to
isZero N/A Check if a number is zero
lshift N/A Bitwise left shift
lt < Less than
lte <= Less than or equal to
mod % Modulo
neq != Not equal operator
not ~ Negation operator
or | Logical OR
rshift N/A Bitwise right shift
sub - Checked subtraction
unary - Checked unary
uncheckedAdd N/A Unchecked addition
uncheckedSub N/A Unchecked 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/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/UD60x18.sol";
import { Assertions as PRBMathAssertions } from "@prb/math/test/Assertions.sol";
import { PRBTest } from "@prb/math/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.

SD59x18 Min Max Avg UD60x18 Min Max Avg
abs 68 72 70 n/a n/a n/a n/a
avg 95 105 100 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
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

ABDKMath64x64

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

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

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 [email protected]:PaulRBerg/prb-math.git

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

$ pnpm install

Now you can start making changes.

Syntax Highlighting

You will need the following VSCode extensions:

Security

While I set a high bar for code quality and test coverage, you should not assume that this project is completely safe to use. PRBMath has not yet been audited by a third-party security researcher.

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

  • Mikhail Vladimirov for the insights he shared in the Math in Solidity article 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.
  • Everyone who has contributed a PR to this repository.

License

This project is licensed under MIT.

More Repositories

1

hardhat-template

Hardhat-based template for developing Solidity smart contracts
TypeScript
1,921
star
2

foundry-template

Foundry-based template for developing Solidity smart contracts
Solidity
485
star
3

prb-proxy

Proxy contract to compose Ethereum transactions
Solidity
270
star
4

prb-test

Modern collection of testing assertions and logging utilities for Solidity
Solidity
178
star
5

prb-contracts

Off-the-shelf Solidity smart contracts
Solidity
130
star
6

multisol

CLI application for verifying Solidity contracts on Etherscan
Rust
113
star
7

rust-template

A template for developing Rust projects, with sensible defaults
Rust
76
star
8

awesome-zkp-starter-pack

A curated collection of links for zero-knowledge proof cryptography used in blockchains
69
star
9

btt-examples

Examples for Branching Tree Technique, a simple specification framework for writing structured Solidity tests
Solidity
52
star
10

evm-bn

Convert fixed-point numbers to ethers big numbers and vice-versa.
TypeScript
49
star
11

check-same-sign

Gas golfing the most gas efficient way to check if two integers have the same sign in Solidity
Solidity
46
star
12

foundry-multibuild

GitHub Action for building a Foundry project with a range of Solidity versions
Shell
38
star
13

hardhat-packager

Hardhat plugin for preparing the contract artifacts and the TypeChain bindings for registry deployment
TypeScript
35
star
14

ethsum

Simple Ethereum address checksum tool
JavaScript
33
star
15

solplate

Simple utility for generating boilerplate Solidity contracts
Rust
32
star
16

typescript-template

A template for developing TypeScript project, with sensible defaults
TypeScript
32
star
17

awesome-many-worlds

Curated collection of educational materials on the many-worlds interpretation of quantum mechanics
23
star
18

CoinMarketCap-Desktop

A simple desktop wrapper for CoinMarketCap
JavaScript
21
star
19

github-labels

GitHub labels to use across repositories
Shell
20
star
20

erc2212

Reference implementation for ERC-2212
JavaScript
12
star
21

unchecked-counter

Solidity counter type that bypasses checked arithmetic
Solidity
12
star
22

contractz

Sketchbook for my Ethereum contracts
Solidity
11
star
23

modlizer

Struggling with iOS design patterns? Embrace Modlizer
Swift
11
star
24

create2-repro

Repo to reproduce the inconsistent CREATE2 behavior in Foundry
Solidity
10
star
25

defi-types

TypeChain bindings for DeFi protocols
Solidity
10
star
26

dyi-crypto-taxes

Spreadsheet templates and practical tips and tricks for doing your taxes alone
10
star
27

abdk-gas-estimations

Gas estimations for ADBKMath64x64
Solidity
9
star
28

rust-book-annotations

My absorption of the Rust Book
Rust
8
star
29

javascript-template

A template for developing JavaScript projects, with sensible defaults
JavaScript
7
star
30

typescript-monorepo-template

Dummy monorepo set up with Typescript and Yarn Workspaces
TypeScript
4
star
31

tft

Browser extension that prevents typos by asking you to double-check the content before posting a long tweet",
JavaScript
3
star
32

cryptfolio-scripts

Google Apps Script functions to help with tracking a crypto portfolio in Google Sheets
JavaScript
3
star
33

foundry-expect-emit-bug

Repo to reproduce the bug I ran into while writing tests with Foundry
Solidity
3
star
34

cea-contracts

Base smart contracts used in the default template of Create Eth App
JavaScript
3
star
35

my-eth-app

Default boilerplate code generated by create-eth-app
HTML
3
star
36

hardhat-prb

Hardhat plugins to complement the PRB smart contract libraries
TypeScript
3
star
37

flipper-ui

The frontend app for the users of Flipper.
JavaScript
2
star
38

react-hooks

React Hooks to be shared across my personal projects and packages
JavaScript
2
star
39

web3-react-blobs

Blobs of web3-react code used in the Sablier Interface
JavaScript
2
star
40

forge-coverage-bug

Repo to reproduce the bug in the forge coverage command
Solidity
2
star
41

PaulRBerg.github.io

My personal preferences
JavaScript
2
star
42

xstate-useActor-bug

xstate useActor bug
TypeScript
2
star
43

prb-proxy-frontend

Landing page for PRBProxy
TypeScript
2
star
44

eslint-config

Eslint config to be shared across my personal projects and packages
JavaScript
2
star
45

airdropx

Firebase backend for the AirdropX Telegram bot
JavaScript
1
star
46

learn-plasma-rss

RSS feed for Plasma resources
JavaScript
1
star
47

paulrberg-labelsync

Dummy repo for label-sync.com
1
star
48

sketchpad

Personal repo for learning, logs and constants
JavaScript
1
star
49

yarn-rc34-error

Dummy project to reproduce the errors I encountered with [email protected]
JavaScript
1
star
50

dfe

Browser extension that hides the price of Ether
JavaScript
1
star
51

cea-template

The official base template for Create Eth App.
HTML
1
star
52

hardhat-ignition-basic

Dummy project for going through the [email protected] basic tutorial
TypeScript
1
star
53

vscode-dictionary

Personal dictionary for vscode-spell-checker
1
star
54

buidler-invalid-opcode

Reproduces the ganache-cli invalid opcode bug
TypeScript
1
star
55

ethiasi

Landing page for the first Ethereum event in Iasi, Romania
CSS
1
star
56

codebase-go

Personal repo for learning, logs and constants related to golang.
Go
1
star
57

ethereum-signatures-progress

A collection of links and resources I collected in order to implement EIP-191 and EIP-712 into geth.
1
star