Signature
log1p(float $num): floatPurpose
log1p() returns the natural logarithm of 1 + num. It exists for one reason: precision near zero. When num is very small, the sum 1 + num loses most or all of the digits of num because a float has limited precision. Calling log(1 + $num) then works from an already damaged value. log1p() avoids that intermediate sum, so the result stays accurate.
Typical uses are tiny growth rates, small probabilities, interest or return calculations, and any formula that contains log(1 + x) where x can be very small.
Signature
Parameters and defaults
num: the value to process. It is required and has no default. The function computes the logarithm of1 + num, so the useful domain isnum >= -1.
In the default non-strict mode an integer or a numeric string is converted to a float. With declare(strict_types=1), integers are still accepted for a float parameter, but strings are rejected.
Return value and failure cases
The function always returns a float: the value of log(1 + num).
log1p(0)gives0.0.log1p(-1)is the logarithm of zero, which is negative infinity.- Any
numbelow-1would need the logarithm of a negative number, so the result isNAN. - A non-numeric string such as
"abc"throws aTypeError.
The function does not raise warnings for out-of-domain input. It returns the special float values instead, so check with is_nan() or is_infinite() when the input comes from outside your code.
Example 1: why it beats log(1 + x)
The smallest difference between 1.0 and the next float is about 2.2e-16. Adding 1e-16 to 1 therefore changes nothing, and log() sees exactly 1.
$x = 1e-16;
var_dump(1 + $x === 1.0);
var_dump(log(1 + $x) === 0.0);
echo round(log1p($x) * 1e16, 6), "\n";bool(true) bool(true) 1
The naive version returns exactly zero, which is wrong. log1p() returns a value very close to 1e-16. Multiplying by 1e16 and rounding shows it is essentially 1.
Example 2: log returns and growth
Finance and statistics often work with log returns. A 5% gain is a factor of 1.05, so its log return is log1p(0.05). Log returns can be added together, and expm1() converts the sum back to an ordinary percentage change.
$returns = [0.10, -0.05, 0.02];
$logs = array_map('log1p', $returns);
echo number_format(log1p(0.05), 6), "\n";
echo number_format(expm1(array_sum($logs)), 6), "\n";0.048790 0.065900
The second line is the combined change of 1.10 * 0.95 * 1.02 - 1, which is 0.0659. Using number_format() keeps the printed text independent of long float tails.
Example 3: boundaries of the domain
var_dump(log1p(0));
var_dump(is_infinite(log1p(-1)));
var_dump(log1p(-1) < 0);
var_dump(is_nan(log1p(-2)));float(0) bool(true) bool(true) bool(true)
Zero maps to zero. Exactly -1 gives negative infinity, and anything lower gives NAN.
Edge cases
- Large values: for large
num,log1p($num)andlog(1 + $num)agree to within normal float rounding. The special function only matters near zero. - Negative tiny values:
log1p(-1e-16)is accurate and slightly negative, whilelog(1 - 1e-16)can collapse to zero. - NAN input:
NANpassed in comes back asNAN. - Comparing floats: never test results with
==unless the value is exactly known, such as0. Round, or compare with a tolerance.
Common mistakes
- Passing the full factor instead of the rate. For a 5% increase, pass
0.05, not1.05.log1p(1.05)is the logarithm of2.05. - Using it for other bases. The result is a natural logarithm. For base 10, use
log10(1 + $x), which does not have the same near-zero protection. - Ignoring
NAN. Values below-1do not throw. They silently produceNAN, which then spreads through later arithmetic. - Printing raw floats in tests. Output such as
0.04879016416943can vary with theprecisionsetting. Format withnumber_format()orround()when exact text matters. - Using it when precision is irrelevant. For moderate values,
log(1 + $x)is fine. Uselog1p()whenxmay be tiny.
Related functions
expm1(): the inverse partner, computingexp($num) - 1accurately near zero.log(): natural logarithm, or logarithm with a chosen base.log10(): base-10 logarithm.exp(): the exponential function.
Recap
log1p($num) returns log(1 + $num) as a float, with accuracy preserved when $num is close to zero. It returns negative infinity for -1 and NAN below -1. Pair it with expm1() for round trips, and pass the rate of change rather than the whole factor.
Tested examples
$x = 1e-16;
var_dump(1 + $x === 1.0);
var_dump(log(1 + $x) === 0.0);
echo round(log1p($x) * 1e16, 6), "\n";bool(true) bool(true) 1
$returns = [0.10, -0.05, 0.02];
$logs = array_map('log1p', $returns);
echo number_format(log1p(0.05), 6), "\n";
echo number_format(expm1(array_sum($logs)), 6), "\n";0.048790 0.065900
var_dump(log1p(0));
var_dump(is_infinite(log1p(-1)));
var_dump(log1p(-1) < 0);
var_dump(is_nan(log1p(-2)));float(0) bool(true) bool(true) bool(true)