Library / SDK
PaulRBerg/prb-math avatar
PaulRBerg/prb-math

prb-math: fixed point math for Solidity, with the type safety that the fast options leave out

Solidity library for advanced fixed-point math

1,007 stars148 forksSolidityMIT

At a glance

What is it?
prb-math is a Solidity library for fixed point arithmetic with 18 trailing decimals, packaged as user-defined value types for signed and unsigned numbers rather than as a Solidity library. This looks at what the type system buys you, how the install works, and where the format constrains you.
Who is it for?
prb-math fits a Solidity team that has to do real arithmetic on chain, that wants the safety of a checked type over a pile of raw integers, and that is willing to pay for the logarithms and exponentials it actually uses rather than for the whole surface.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 3 days ago.
What is it written in?
Mainly Solidity, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Why fixed point at all

prb-math is a Solidity library for advanced fixed point math, and the reason it exists is that floating point is not available on the EVM. There is no `float`, and no decimal type, and a DeFi contract that needs to compute an interest rate, a share price or a geometric mean has to do that arithmetic on integers.

Doing it naively, by scaling by 10^18 by hand at every call site, is where bugs live. A missing scale, a rounding direction that differs between two functions, a division that truncates when it should round half up: each of those is a contract that behaves differently from its specification.

The number format here is signed 59.18-decimal and unsigned 60.18-decimal fixed point, and the readme explains the name: the integer part has up to 59 digits for signed numbers and 60 for unsigned, while the fractional part has up to 18 decimals, and the numbers are bound by the minimum and maximum values permitted by the Solidity types `int256` and `uint256`.

That last sentence is the design in one line. The format is chosen to fill the width of the underlying integer type as exactly as possible, which means the arithmetic is as precise as the storage allows, and the range is as wide as the storage allows, and neither is limited by a smaller intermediate type.

The audience is Solidity developers on the EVM, and the topic tags place it squarely in decentralised finance, smart contracts and the Ethereum ecosystem.

User-defined value types, and a deliberate refusal of the library pattern

There are two user-defined value types, `SD59x18` for signed numbers and `UD60x18` for unsigned ones. The readme points at the Solidity blog post on user-defined value types for anyone who does not know what they are, and the reasoning is worth stating: a user-defined value type is a distinct type in the compiler, so an `SD59x18` cannot be passed where a `UD60x18` or a raw `uint256` is expected, and the compiler will refuse it.

The next point is a correction of a common assumption. PRBMath is not a library in the Solidity sense. It is just a collection of free functions. That means no `using ... for` pattern is required, and it also means the functions are called directly, which is what the readme means by an ergonomic developer experience thanks to using free functions instead of libraries.

The readme also gives a rule that is more about maintenance than mathematics. It is recommended that you import PRBMath using specific symbols, because 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.

solidity
pragma solidity >=0.8.19;

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

And any function that is not available in the types directly has to be imported explicitly, which is why the `sd` and `ud` constructor functions are named in the import list alongside the types. The version floor is stated plainly: PRBMath can only be used in Solidity v0.8.19 and above.

One more practical note. If you do not need negative numbers, there is no point in using the signed flavour, because the unsigned flavour is more gas efficient.

What the type gives you in practice

The usage examples are short, and they show the shape of the API.

solidity
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();
  }
}

The `sd` and `ud` functions are the constructors, and they take a scaled literal such as `0.05e18`, which is the fixed point notation for five percent. The arithmetic then reads as method calls on the type, `x.mul(fivePercent)` and `x.log2()`, which is the ergonomics the feature list is talking about.

The mathematical surface is in the features table, and because the signed and unsigned types overlap heavily there is one table per section rather than two. The functions cover `abs`, `avg`, `ceil`, `div`, `exp`, `exp2`, `floor`, `frac`, `gm` for geometric mean, `inv`, `ln`, `log10`, `log2`, `mul`, `pow` and `powu`.

The other entries in the feature list are the ones that matter for a contract. It provides type safety via user-defined value types. It bakes in overflow-safe multiplication and division via `mulDiv`, which is the function that computes `a * b / c` without an intermediate overflow, a classic source of vulnerability in fixed point code. It reverts with custom errors instead of reason strings, which is cheaper in bytecode and machine readable. And it is well-documented with NatSpec comments, which the readme says you should consult when in doubt, since the source is the reference.

Installing with a package manager, or as a submodule

The recommended route is the Node.js package manager route, and the readme gives Bun as the example because that is what the author uses.

shell
bun add @prb/math

If you are using Foundry, you then add this to your `remappings.txt` file:

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

The git submodule method is explicitly not recommended but is available for those who prefer it. You install the submodule with Forge:

shell
forge install PaulRBerg/prb-math@release-v4

Your `.gitmodules` file should then contain the entry:

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

and the remapping becomes:

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

The `release-v4` branch is the stable line, which is what you want for a contract, and the difference between the two methods is where the code lives on disk rather than how it is consumed.

The published package is `@prb/math`, version 4.2.1 in the manifest, and it ships the `src` directory, `test/utils` and the changelog. The dependencies are minimal for a Solidity library, since a fixed point implementation is pure arithmetic with no runtime dependencies, which is exactly why it can be audited by reading it.

Building and testing with just and Foundry

The repository uses Foundry for the build and the tests, and `just` as a task runner on top of it, and the `justfile` in the root makes the workflow easy to read.

The commands are grouped. The build group has `@build` running `forge build`, with the alias `b`, and `@clean` running `bunx del-cli cache out`, with the alias `c`. The test group has `@test *args` running `forge test` with arguments passed through, aliased to `t`. The checks group has `@full-check` running the prettier check, the solhint check and the forge check in sequence.

bash
just build
just test
just full-check

The file also sets `set shell := ["bash", "-euo", "pipefail", "-c"]`, so every recipe runs with strict shell options and fails on the first error, and it declares `bun` and `forge` as required tools through `require()`.

Around that sits a conventional setup: `foundry.toml` for the Foundry configuration, `solhint.json` for Solidity linting, a Prettier configuration for the JSON, Markdown and YAML files, a `bun.lock` for the JavaScript side, and separate `src/` and `test/` directories. There is also an `AGENTS.md` and a `SECURITY.md` at the root, which for a library that ends up holding value in a contract is the more important of the two.

The lint setup deserves a note. `solhint` and `prettier` are both run in the check group, and the Prettier globs are scoped to JSON, Markdown and YAML, while the solhint globs are scoped to the Solidity sources under `src` and `test`. Formatting and linting are therefore separate concerns, which is the usual arrangement for Solidity projects where the formatter would fight the linter over line length.

The alternatives, and the trade the readme is making

The readme names two comparable libraries and states the trade-off in one paragraph, which is worth reading in full because it is the clearest statement of the design intent.

ABDKMath64x64 is fast, but uses binary numbers, which are counter-intuitive. Solmate is fast and intuitive, but lacks type safety. And PRBMath is described as a fixed point math library that is at the same time intuitive, efficient and safe.

That is the whole argument, and it is a position rather than a measurement. A 64.64 binary fixed point representation is genuinely cheaper to compute with than a 59.18 denary one, because shifting by 64 is a bit operation while scaling by 10^18 is a multiplication, and if your application only ever does addition, subtraction, multiplication and division, the choice of base can matter to your gas bill.

What PRBMath buys with that cost is a representation a human reads the way a decimal one is read. 0.05 is 0.05 in both, but the underlying integers differ, and a reviewer looking at a diff of scaled values has a chance of spotting a decimal scaling error. The type system then makes it impossible to pass a signed value where an unsigned one belongs.

So the choice is between gas, familiarity and safety, and the readme takes familiarity and safety. For a DeFi contract where a rounding bug costs money, that is a reasonable place to spend, and it is a decision a team should make explicitly rather than inherit from a library choice.

Where prb-math is the wrong tool

Four limits are clear.

The first is the eighteen decimals. The format is fixed, and if your application needs a different scale, or needs precision beyond eighteen fractional digits, this library is the wrong shape and you want a custom implementation. There is also no arbitrary precision mode, because the format is deliberately pinned to the width of `int256` and `uint256`.

The second is that the safety is a language feature. User-defined value types are a Solidity capability, and the type safety argument only holds inside Solidity code that uses the types. A value that crosses a boundary, through an ABI, into another contract or off chain, is an `int256` again, and the rounding decision you made at the boundary is yours to make.

The third is the cost of the overflow-safe path. `mulDiv` is the function that removes the intermediate overflow, and it is necessarily more expensive than a plain multiply. In a hot path, called per user or per block, that cost is real, and the readme does not publish gas numbers for the individual functions, so the only way to know is to measure it in your own contract.

The fourth is the Solidity floor. PRBMath can only be used in Solidity v0.8.19 and above, which rules out older contracts and, more importantly, chains whose compilers are behind that. User-defined value types arrived in Solidity 0.8.8, and the library's own floor is higher, so a chain without a recent Solidity toolchain cannot use it at all.

On maintenance, the project is active: v4.1.2 was published on 2026-05-30, v4.2.0 on 2026-08-03, and v4.2.1 on 2026-10-12, with the last push to the repository on the same day. The licence is MIT, with the licence text in `LICENSE.md` at the root.

Editorial conclusion

prb-math fits a Solidity team that has to do real arithmetic on chain, that wants the safety of a checked type over a pile of raw integers, and that is willing to pay for the logarithms and exponentials it actually uses rather than for the whole surface. It does not fit a contract whose values need arbitrary precision or more than eighteen decimals, and it is the wrong tool for a chain that lacks user-defined value types, since the type safety is the feature and the feature is a Solidity language capability. Before you adopt it, read the feature table and count the functions you will call, and check the `mulDiv` path for the multiplication whose intermediate result exceeds the type, because that is where the cost of the safety shows up.

Frequently asked questions

How do I install prb-math?

The recommended route is the package manager route, for example `bun add @prb/math`, and then adding `@prb/math/=node_modules/@prb/math/` to your remappings.txt if you use Foundry. A submodule route is also documented with `forge install PaulRBerg/prb-math@release-v4`, though the readme says it is not recommended.

What is the difference between SD59x18 and UD60x18?

They are the signed and unsigned user-defined value types, with 59 and 60 integer digits respectively and 18 fractional digits. The readme notes that if you do not need negative numbers there is no point in using the signed flavour, because the unsigned one is more gas efficient.

Is prb-math a Solidity library?

No, and the readme is explicit about it. PRBMath is not a library in the Solidity sense, it is just a collection of free functions, which is why there is no using-for pattern and why the arithmetic reads as method calls on the type.

Which Solidity versions does prb-math support?

PRBMath can only be used in Solidity v0.8.19 and above, and the example imports in the documentation use `pragma solidity >=0.8.19;` for that reason.

How does prb-math compare to ABDKMath64x64 and Solmate?

The readme says ABDKMath64x64 is fast but uses binary numbers, which are counter-intuitive, and that Solmate is fast and intuitive but lacks type safety, while PRBMath aims to be intuitive, efficient and safe through user-defined value types and overflow-safe mulDiv.

Official sources

  1. Issues
  2. License: MIT
  3. PaulRBerg/prb-math on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/paulrberg-prb-math.svg)](https://hysenlabs.com/projects/paulrberg-prb-math)

Was this useful?