log1p

Math

Computes 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

solidity
function log1p(int256 x) internal pure returns (int256 y)

Parameters

NameTypeDescription
xint256Signed input in 18-decimal fixed-point format (1e18 = 1.0). Must satisfy x > −1e18 so that 1 + x > 0.

Returns

NameTypeDescription
yint256Result ln(1 + x) in 18-decimal fixed-point format. Signed — returns negative values for x < 0.

Bounds

BoundValue
Input domainSigned 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 series x − x²/2 + x³/3 − … − x¹⁰/10, accurate to ~1e-21 truncation error at the interval boundary.
  • For |x| ≥ 0.01: falls through to ln(uint256(1e18 + x)), which has sufficient headroom that forming 1 + x no longer loses precision.
  • Reverts with Log1pLowerBoundError() when x ≤ −1e18 — the domain requires 1 + x > 0.
  • Returns 0 exactly when x == 0 (Taylor series with x = 0 collapses cleanly).
  • Pure internal function; 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¹¹ / 119e-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

ErrorTrigger
Log1pLowerBoundErrorx ≤ −1e18

Example

solidity
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))