log1p
MathComputes ln(1 + x) while preserving full 18-digit precision near zero, where forming 1 + x for tiny x would lose most of x's significant digits.
Gas
500
Max rel. error
7.0e-15
Signature
function log1p(int256 x) internal pure returns (int256 y)Parameters
| Name | Type | Description |
|---|---|---|
| x | int256 | Signed input in 18-decimal fixed-point format (1e18 = 1.0). Must satisfy x > −1e18 so that 1 + x > 0. |
Returns
| Name | Type | Description |
|---|---|---|
| y | int256 | Result ln(1 + x) in 18-decimal fixed-point format. Signed — returns negative values for x < 0. |
Bounds
| Bound | Value |
|---|---|
| Input domain | Signed int256 with x > −1e18 (equivalently 1 + x > 0) — no named upper bound. The lower bound is enforced via Errors below. |
Behavior
- For
|x| < 0.01: evaluates a 10-term alternating Taylor seriesx − x²/2 + x³/3 − … − x¹⁰/10, accurate to ~1e-21 truncation error at the interval boundary. - For
|x| ≥ 0.01: falls through toln(uint256(1e18 + x)), which has sufficient headroom that forming1 + xno longer loses precision. - Reverts with
Log1pLowerBoundError()whenx ≤ −1e18— the domain requires1 + x > 0. - Returns 0 exactly when
x == 0(Taylor series withx = 0collapses cleanly). - Pure
internalfunction; no external calls or storage.
How it works
Computing ln(1 + x) naively via ln(1e18 + x) is precise everywhere except near zero, where 1e18 + x rounds away most of x's significant digits before ln even sees it. At x = 1e3 (i.e. 10⁻¹⁵ in real terms), the addition 1e18 + 1e3 preserves only 3 of x's digits — the rest are lost to representational precision. The result ln returns is dominated by rounding noise.
log1p sidesteps this by switching strategies on the magnitude of x. For |x| < 0.01 we evaluate the Maclaurin series directly:
ln(1 + x) = x − x²/2 + x³/3 − x⁴/4 + … − x¹⁰/10
With 10 terms the truncation error at the interval boundary (|x| = 0.01) is on the order of 0.01¹¹ / 11 ≈ 9e-24, well below 18-digit precision. Every term is a single integer multiply-and-divide, and crucially x never has to be added to 1 — its digits are preserved through every term.
For |x| ≥ 0.01 the cancellation problem disappears: 1 + x no longer rounds away x's digits, so the function falls through to ln(uint256(1e18 + x)) and inherits ln's ~1e-14 precision. The split point at 0.01 is where the two error profiles meet.
The domain check x > −1e18 guards ln's domain: ln is only defined for positive arguments, so 1 + x must be strictly positive. The function reverts cleanly with Log1pLowerBoundError() on violations rather than passing an invalid argument down the stack.
Errors
| Error | Trigger |
|---|---|
| Log1pLowerBoundError | x ≤ −1e18 |
Example
import "defimath-lib/contracts/math/Math.sol";
int256 x = 1e15; // x = 0.001 (small input — Taylor branch)
int256 result = DeFiMath.log1p(x); // result ≈ 9.995e14 (≈ 0.0009995)
int256 y = 1e18; // y = 1.0 (ln fallback branch)
int256 ly = DeFiMath.log1p(y); // ly ≈ 0.69315e18 (= ln(2))