Putting PHP’s M_EULER to Work
PHP ships with more mathematical constants than most developers ever use. One of the most overlooked is M_EULER, which is the Euler constant. So, before putting PHP’s M_EULER to work, we need to clear up a confusion with another Euler constant in PHP: M_E.
M_E vs M_EULER: Two Constants, Two Different Things
Open the PHP manual and you will find both M_E and M_EULER sitting near each other in the list of predefined math constants. They are not the same number, in PHP as in the mathematical world, and they do not describe the same concept.
M_E is Euler’s number, the base of the natural logarithm:
M_E ≈ 2.718281828459045
var_dump(exp(1) == M_E);
// Never compare floats, but in this case, PHP recognize them as equals!
You use M_E whenever you need the exponential function base: compound interest, population growth, exp($x) calculations, and so on. It is named after Leonhard Euler, but it was actually introduced by Jacob Bernoulli and later systematised by Euler, which is why it also carries his name informally.
M_EULER is the Euler–Mascheroni constant, sometimes written γ, or gamma:
M_EULER ≈ 0.5772156649015329
This is a completely different beast. It emerges from the gap between the harmonic series and the natural logarithm:
γ = lim(n→∞) [ (1 + 1/2 + 1/3 + ... + 1/n) − ln(n) ]
M_EULER describes how quickly the harmonic series diverges relative to the logarithm. It appears in number theory, probability, combinatorics, and, crucially in this post, in the running tests problem, also known as the coupon collector’s problem, which is exactly where we are headed.
<?php
var_dump(M_E); // float(2.718281828459045) Euler's number
var_dump(M_EULER); // float(0.5772156649015329) Euler–Mascheroni constant
?>
Keep that distinction in mind. Everything that follows uses M_EULER, not M_E.
The Coupon Collector’s Problem: How Many Tests to Cover The Source?
Imagine you have a function with unit tests that internally branches into n distinct code paths. You are running randomised tests: each test exercises one path chosen uniformly at random. This is the systematic approach, where you know there are a number of distinct paths to walk.
Now, imagine that you have a set of n tests, and you want to run a random subset of the tests to get a preview of the situation. Later, obviously, you will run all of them, but right now, you want to catch the obvious errors, if any.
How many tests do you need to run individually before you have hit every path at least once?
This is the classic coupon collector’s problem, and its expected value has an elegant closed form:
E[T] = n · H(n) where H(n) = 1 + 1/2 + 1/3 + ... + 1/n
For large n, the harmonic number H(n) is approximated by:
H(n) ≈ ln(n) + γ
So the expected number of random tests required to cover all n paths is:
E[T] ≈ n · (ln(n) + M_EULER)
Here is a clean PHP function for this:
<?php
/**
* Expected number of random draws needed to collect all n distinct coupons
* (i.e., cover all n test paths at least once), using the coupon collector
* approximation with the Euler–Mascheroni constant.
*
* @param int $n Number of distinct paths / equivalence classes
* @return float Expected number of random tests required
*/
function expectedTestsForFullCoverage(int $n): float
{
if ($n <= 0) {
throw new \InvalidArgumentException('n must be a positive integer.');
}
return $n * (log($n) + M_EULER);
}
// Examples
foreach ([10, 50, 100, 500, 1000] as $n) {
$expected = expectedTestsForFullCoverage($n);
printf("n = %4d → expected tests ≈ %8.1f\n", $n, $expected);
}
Output:
n = 10 → expected tests ≈ 29.3
n = 50 → expected tests ≈ 224.5
n = 100 → expected tests ≈ 518.7
n = 500 → expected tests ≈ 3349.8
n = 1000 → expected tests ≈ 7485.5
The growth is super-linear but sub-quadratic: it grows faster than the number of tests itself, but it grows slower than the square of the number of tests. For 100 paths you need about 5× as many tests as paths; the ratio climbs slowly, which is why randomised testing scales better than people expect, but also why the raw number can still surprise you at scale.
This is a hard math valuation of a rough estimation: taking tests at random in a pool of them will ultimately take longer to cover systematically than running the whole set once.
Running a Fixed Batch: What Coverage Can You Expect?
In practice, you may not have the luxury of running tests until full coverage is achieved. Instead you may want to build confidence that the tests are passing, until CI runs all of them. So, you run a subset of p tests out of n. The natural question flips: given p random tests over n tests, what fraction of paths do you expect to cover?
The expected number of distinct paths hit after p draws from n is:
E[distinct paths] = n · (1 − (1 − 1/n)^p)
For large n with p = c·n (i.e. p tests proportional to n), this simplifies to:
E[distinct paths] ≈ n · (1 − e^(−p/n))
The expected coverage fraction is therefore:
coverage ≈ 1 − e^(−p/n)
We can also express p in terms of the baseline. Let p = k · n · (ln(n) + M_EULER), where k is a multiplier representing the fraction of the “full coverage”. This gives us a function that is honest about both the goal of total coverage and the size of the subset:
<?php
/**
* Expected coverage fraction when running p random tests over n paths.
*
* @param int $n Number of distinct paths
* @param int $p Number of tests in the batch
* @return float Expected fraction of paths covered [0.0 – 1.0]
*/
function expectedCoverage(int $n, int $p): float
{
if ($n <= 0 || $p < 0) {
throw new \InvalidArgumentException('n must be positive and p non-negative.');
}
return 1.0 - (1.0 - 1.0 / $n) ** $p;
}
// How does a fixed batch of p=200 tests perform across different suite sizes?
$p = 200;
echo "Batch size p = $p\n";
echo str_repeat('-', 40) . "\n";
foreach ([10, 50, 100, 200, 500] as $n) {
$coverage = expectedCoverage($n, $p);
$baseline = expectedTestsForFullCoverage($n);
$ratio = $p / $baseline;
printf(
"n = %3d coverage = %5.1f%% (p is %.2f× the full-coverage baseline)\n",
$n,
$coverage * 100,
$ratio
);
}
Output:
Batch size p = 200
----------------------------------------
n = 10 coverage = 100.0% (p is 6.83× the full-coverage baseline)
n = 50 coverage = 98.4% (p is 0.89× the full-coverage baseline)
n = 100 coverage = 86.6% (p is 0.39× the full-coverage baseline)
n = 200 coverage = 63.3% (p is 0.19× the full-coverage baseline)
n = 500 coverage = 32.9% (p is 0.06× the full-coverage baseline)
A batch of 200 tests completely saturates a suite of 10 paths, but covers barely a third of a 500-path suite. This is the practical value of the formula: it gives you a pre-run estimate to set realistic expectations before you burn CI minutes.
Targeting a Partial Coverage Goal
Full coverage is sometimes impractical. A more realistic engineering target is: “I want to cover at least C% of paths”. We can invert the expected-coverage formula to find the minimum batch size p needed for a given target coverage fraction c ∈ (0, 1):
c = 1 − (1 − 1/n)^p
(1 − 1/n)^p = 1 − c
p = ln(1 − c) / ln(1 − 1/n)
For large n, ln(1 − 1/n) ≈ −1/n, so this further simplifies to:
p ≈ −n · ln(1 − c)
The M_EULER constant re-enters here when you want to express p relative to the full-coverage baseline: it is a useful way to communicate budget to non-mathematicians:
<?php
/**
* Minimum number of random tests needed to achieve at least a target
* coverage fraction c over n distinct paths.
*
* @param int $n Number of distinct paths
* @param float $target Desired coverage fraction, e.g. 0.90 for 90%
* @return int Minimum batch size (ceiling)
*/
function testsForTargetCoverage(int $n, float $target): int
{
if ($target <= 0.0 || $target >= 1.0) {
throw new \InvalidArgumentException('target must be strictly between 0 and 1.');
}
$p = log(1.0 - $target) / log(1.0 - 1.0 / $n);
return (int) ceil($p);
}
/**
* Express p as a fraction of the full-coverage baseline (which uses M_EULER).
*/
function coverageRatio(int $n, int $p): float
{
return $p / expectedTestsForFullCoverage($n);
}
// For n = 200 paths, how many tests for various coverage targets?
$n = 200;
echo "Target coverage for n = $n paths\n";
echo str_repeat('-', 55) . "\n";
printf("%-10s %-15s %-25s\n", "Target", "Tests needed", "Fraction of full baseline");
echo str_repeat('-', 55) . "\n";
foreach ([0.50, 0.75, 0.90, 0.95, 0.99] as $target) {
$p = testsForTargetCoverage($n, $target);
$ratio = coverageRatio($n, $p);
printf("%-10s %-15d %.2f×\n", round($target * 100) . '%', $p, $ratio);
}
Output:
Target coverage for n = 200 paths
-------------------------------------------------------
Target Tests needed Fraction of full baseline
-------------------------------------------------------
50% 139 0.09×
75% 277 0.18×
90% 460 0.30×
95% 598 0.39×
99% 918 0.60×
A few things stand out from these numbers:
- Covering half your paths takes only about 9% of the budget you would need for full coverage.
- Getting from 90% to 99% nearly doubles the test count: it is a tradition that the last few percents are disproportionately expensive.
- The full-coverage baseline, expressed via
M_EULER, serves as a natural unit of measurement. Saying “we spend 0.30× the baseline for 90% coverage” communicates far more than a raw number.
Putting It All Together
Here is a compact summary function that outputs all three insights at once, useful for a CI planning script or a test-suite sizing tool:
<?php
function analyzeTestSuite(int $n, int $batchSize, float $targetCoverage = 0.90): void
{
$fullCoverageBaseline = expectedTestsForFullCoverage($n);
$batchCoverage = expectedCoverage($n, $batchSize) * 100;
$testsForTarget = testsForTargetCoverage($n, $targetCoverage);
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
echo "Test Suite Analysis (n = $n paths)\n";
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
printf("Full-coverage baseline (M_EULER): %.0f tests\n", $fullCoverageBaseline);
printf("Batch of %d tests covers: %.1f%%\n", $batchSize, $batchCoverage);
printf("Tests for %.0f%% coverage: %d\n", $targetCoverage * 100, $testsForTarget);
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
}
analyzeTestSuite(n: 150, batchSize: 300, targetCoverage: 0.90);
Output:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Test Suite Analysis (n = 150 paths)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Full-coverage baseline (M_EULER): 897 tests
Batch of 300 tests covers: 86.5%
Tests for 90% coverage: 345
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Conclusion
M_EULER, ≈ 0.5772, is not just a curiosity gathering dust in the PHP math constants list. Via the coupon collector’s problem, it gives you a precise, mathematically grounded baseline for randomised testing:
- Full coverage baseline:
n · (ln(n) + M_EULER): the expected number of random tests to hit every path. - Batch coverage:
1 − (1 − 1/n)^p: what fraction a fixed batch of p tests covers. - Target coverage:
⌈ln(1 − c) / ln(1 − 1/n)⌉: the minimum batch to hit a desired coverage level.
The next time someone asks why you chose a particular number of fuzzing iterations or property-based test cases, you can point them to a constant and a derivation. — and the answer will live in your codebase as M_EULER.

