Math, Numbers and Randomness

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against PHP 8.5 (with the bcmath and gmp extensions), checked on 2026-10-05; the clamp() example was run on the PHP 8.6 release candidate. Integer and float types themselves are described in Types and the Type System.

The math functions

The core math functions are always available:

Function Notes

abs(), max(), min()

max()/min() accept several arguments or one array

intdiv($a, $b)

integer division rounding towards zero; throws DivisionByZeroError and ArithmeticError (for PHP_INT_MIN / -1)

fmod($a, $b)

float remainder (the % operator truncates operands to int)

fdiv($a, $b)

IEEE 754 division: returns INF, -INF or NAN instead of throwing (8.0)

fpow($base, $exp)

IEEE 754 exponentiation for floats (8.4); 0 ** -1 with ** is deprecated in 8.4 in favour of it

round($n, $precision, $mode)

rounding to a precision (negative for tens, hundreds); $mode is a RoundingMode enum since 8.4

floor(), ceil()

always return float for float input

sqrt(), pow(), exp(), log(), log10(), hypot(), trigonometric functions

the usual

base_convert(), bindec(), decbin(), hexdec(), dechex(), octdec(), decoct()

base conversion (base_convert() works through floats and loses precision for large values)

is_nan(), is_finite(), is_infinite()

checks for special float values

M_PI, M_E, M_SQRT2, PHP_FLOAT_EPSILON, …

constants

<?php

declare(strict_types=1);

var_dump(intdiv(7, 2), fmod(7.5, 2), fdiv(1, 0), fpow(2.0, 0.5));   // int(3) float(1.5) float(INF) float(1.414…)

// Rounding modes (8.4): default is HalfAwayFromZero
var_dump(round(2.5), round(-2.5), round(2.5, 0, RoundingMode::HalfEven), round(3.5, 0, RoundingMode::HalfEven));
// float(3) float(-3) float(2) float(4)
var_dump(round(1234.5678, -2), floor(-1.5), ceil(-1.5));             // float(1200) float(-2) float(-1)

var_dump(bindec('1010'), dechex(255), octdec('755'), base_convert('ff', 16, 2));   // 10 "ff" 493 "11111111"
var_dump(hypot(3, 4), is_nan(sqrt(-1)));                             // float(5) bool(true)

clamp() (upcoming in PHP 8.6)

clamp($value, $min, $max) limits a value to a range, replacing the max($min, min($max, $value)) idiom; it throws ValueError if $min is greater than $max.

<?php

declare(strict_types=1);

// requires PHP 8.6
var_dump(clamp(15, 0, 10), clamp(-1, 0, 10), clamp(5.5, 0, 10));   // int(10) int(0) float(5.5)

Source: RFC: clamp() v2

Floating-point pitfalls with money

Floats are binary fractions: 0.1 cannot be represented exactly, so sums drift and rounding at a boundary depends on invisible digits. Never store or compute money as floats. Store integer minor units (cents), or use arbitrary precision decimals.

<?php

declare(strict_types=1);

var_dump(0.1 + 0.2);                          // float(0.30000000000000004)
var_dump(number_format(0.1 + 0.2, 20));       // "0.30000000000000004441"
var_dump(array_sum(array_fill(0, 10, 0.1)) == 1.0);   // bool(false)

// Integer cents: exact
$fineCents = 3 * 25 + 199;
printf("%d.%02d EUR\n", intdiv($fineCents, 100), $fineCents % 100);   // 2.74 EUR

Arbitrary precision: BCMath and BcMath\Number

The BCMath extension computes with decimal numbers represented as strings, with an explicit scale (number of decimal digits). Functions include bcadd, bcsub, bcmul, bcdiv, bcmod, bcpow, bcsqrt, bccomp and, since 8.4, bcround, bcfloor, bcceil and bcdivmod.

PHP 8.4 added the BcMath\Number class: an immutable decimal object that supports the arithmetic and comparison operators directly, keeps track of the scale, and converts to string. It is the most convenient way to handle money or any exact decimal arithmetic in pure PHP.

<?php

declare(strict_types=1);

use BcMath\Number;

var_dump(bcadd('0.1', '0.2', 2));           // string(4) "0.30" -- exact
var_dump(bcdiv('10', '3', 4));              // string(6) "3.3333"
var_dump(bcpow('2', '64'));                 // string(20) "18446744073709551616"
var_dump(bcround('1.955', 2));              // string(4) "1.96" (8.4)

$price = new Number('19.99');
$total = $price * 3 + new Number('0.03');   // operators work on Number objects (8.4)
var_dump((string) $total, $total->scale);  // string(5) "60.00" int(2)
var_dump($total == new Number('60'));       // bool(true): compares values
var_dump((string) $total->round(0, RoundingMode::HalfEven));   // string(2) "60"

Division with Number chooses a scale large enough for the result (up to a limit), so call round() explicitly before presenting or storing a value. Libraries such as brick/money and moneyphp/money add currencies and allocation on top.

Big integers: GMP

GMP (GNU Multiple Precision) handles arbitrarily large integers very fast. GMP objects support arithmetic and comparison operators, and functions cover number theory: modular arithmetic, primality tests, factorials, GCD, bit operations and base conversion.

<?php

declare(strict_types=1);

$n = gmp_init('123456789012345678901234567890');
var_dump(gmp_strval($n * 2));                 // string(30) "246913578024691357802469135780"
var_dump(gmp_strval(gmp_mod($n, 97)));        // string(2) "52"
var_dump(gmp_strval(gmp_fact(25)));           // string(26) "15511210043330985984000000"
var_dump(gmp_prob_prime(104729));             // int(2): definitely prime

var_dump(PHP_INT_MAX + 1);                    // float(9.223372036854776E+18): native ints overflow to float

Use GMP for integers beyond 64 bits (identifiers, cryptographic values, combinatorics) and BCMath for decimals.

Source: GMP

Randomness

PHP has three generations of random number APIs:

API Use

random_int($min, $max), random_bytes($n) (7.0)

cryptographically secure: tokens, passwords, keys, shuffling for security

the Random extension, Random\Randomizer (8.2)

object-oriented, chooses an engine: secure by default, or seeded and reproducible

rand(), mt_rand(), mt_srand(), lcg_value(), uniqid()

legacy, not secure; lcg_value() is deprecated since 8.4

Random\Randomizer wraps an engine:

  • Random\Engine\Secure (the default when none is passed) — the operating system’s CSPRNG, like random_bytes();

  • Random\Engine\Xoshiro256StarStar and Random\Engine\PcgOneseq128XslRr64 — fast, seedable, for reproducible simulations and tests;

  • Random\Engine\Mt19937 — Mersenne Twister, compatible with mt_rand().

Its methods include getInt(), nextInt(), getFloat() with an IntervalBoundary (8.3), nextFloat(), getBytes(), getBytesFromString() (8.3, random strings from an alphabet), shuffleArray(), shuffleBytes() and pickArrayKeys().

<?php

declare(strict_types=1);

use Random\Engine\Xoshiro256StarStar;
use Random\IntervalBoundary;
use Random\Randomizer;

// Secure: a library-card activation code from an unambiguous alphabet
$secure = new Randomizer();                                          // Secure engine
$alphabet = implode('', array_diff([...range('A', 'Z'), ...range('2', '9')], ['I', 'O']));   // no I/1, O/0
$code = $secure->getBytesFromString($alphabet, 8);
echo strlen($code), ' chars: ', ctype_alnum($code) ? 'ok' : 'bad', "\n";

$token = bin2hex(random_bytes(16));                                  // 32 hex chars, for a reset link
echo strlen($token), "\n";

// Reproducible: the same seed always gives the same "random" sample (tests, simulations)
$seeded = new Randomizer(new Xoshiro256StarStar(42));
$again = new Randomizer(new Xoshiro256StarStar(42));
var_dump($seeded->getInt(1, 100) === $again->getInt(1, 100));        // bool(true)

$float = $secure->getFloat(0, 1, IntervalBoundary::ClosedOpen);      // [0, 1)
var_dump($float >= 0 && $float < 1);                                 // bool(true)
var_dump(count($secure->pickArrayKeys(['Solaris' => 1, 'Kindred' => 1, 'Dawn' => 1], 2)));   // int(2): two random keys

Never use rand(), mt_rand(), uniqid() or md5(microtime()) for anything security-related: they are predictable. Password hashing and token handling are covered in Cryptography, Hashing and Passwords.

Where older books and tutorials are outdated

  • rand()/mt_rand() for tokens and md5(uniqid(rand())) (Kromann ch. 14, Wiley ch. 8) are insecure; use random_bytes()/random_int() or a Randomizer with the secure engine.

  • round() with integer PHP_ROUND_* constants still works, but the RoundingMode enum (8.4) is preferred and adds modes such as TowardsZero.

  • BCMath only with functions: BcMath\Number and its operators (8.4), bcround()/bcfloor()/bcceil() (8.4) are new.

  • fpow() (8.4), clamp() (8.6) and the Random extension (8.2) are absent from every book.

  • lcg_value() is deprecated since 8.4.