Math #

PHP has a rich math library — from basic operations like abs() and round(), to trigonometric functions, logarithms, and two high-precision libraries: BCMath for arbitrary-precision decimals and GMP for very large integers. What PHP developers often overlook is floating-point precision — 0.1 + 0.2 !== 0.3 isn’t a PHP bug, it’s a fundamental property of the IEEE 754 representation that applies in every language. Knowing when to use regular floats and when to switch to BCMath is the difference between a correct financial application and one with subtle, very expensive bugs.

Basic Operations #

<?php
// Absolute value
echo abs(-42);      // 42
echo abs(-3.14);    // 3.14
echo abs(42);       // 42

// Modulo — already covered in operators, but fmod() exists for floats
echo 10 % 3;        // 1 (integer modulo)
echo fmod(10.5, 3); // 1.5 (float modulo)
echo fmod(-10, 3);  // -1 (sign follows the dividend)

// Integer division (PHP 7+)
echo intdiv(17, 5);  // 3 (rounded down)
echo intdiv(-17, 5); // -3 (not -4!)

// Powers
echo pow(2, 10);    // 1024
echo 2 ** 10;       // 1024 (operator, more idiomatic)
echo sqrt(144);     // 12.0
echo sqrt(2);       // 1.4142135623731

// Logarithms
echo log(M_E);       // 1.0 — natural log
echo log(100, 10);   // 2.0 — log base 10
echo log10(1000);    // 3.0 — log base 10 shorthand
echo log(8, 2);      // 3.0 — log base 2
echo log2(8);        // 3.0 — log base 2 shorthand (PHP 5.6+)

// Min and max
echo min(3, 1, 4, 1, 5, 9, 2, 6); // 1
echo max(3, 1, 4, 1, 5, 9, 2, 6); // 9

// From arrays
$numbers = [5, 3, 8, 1, 9, 2, 7];
echo min($numbers); // 1
echo max($numbers); // 9

// Array sums
echo array_sum($numbers);     // 35
echo array_product([1,2,3,4,5]); // 120 (1×2×3×4×5)

Rounding #

PHP has four different rounding strategies — choosing the wrong one can produce inaccurate calculations:

<?php
// round() — round to a specific number of decimals
echo round(3.4);    // 3.0 — down (< 0.5)
echo round(3.5);    // 4.0 — up (>= 0.5)
echo round(3.55, 1); // 3.6
echo round(3.45, 1); // 3.5 — can surprise because 3.45 is actually 3.4499...

// Decimal precision
echo round(1234.5678, 2);  // 1234.57
echo round(1234.5678, -2); // 1200.0 — round to hundreds

// Rounding modes
echo round(2.5, 0, PHP_ROUND_HALF_UP);    // 3 — standard (default)
echo round(2.5, 0, PHP_ROUND_HALF_DOWN);  // 2 — down at exactly 0.5
echo round(2.5, 0, PHP_ROUND_HALF_EVEN);  // 2 — banker's rounding (to even)
echo round(3.5, 0, PHP_ROUND_HALF_EVEN);  // 4
echo round(2.5, 0, PHP_ROUND_HALF_ODD);   // 3 — to odd

// ceil() — always up (ceiling)
echo ceil(4.1);   // 5.0
echo ceil(4.9);   // 5.0
echo ceil(-4.1);  // -4.0 (up = toward 0 for negatives)
echo ceil(-4.9);  // -4.0

// floor() — always down (floor)
echo floor(4.9);  // 4.0
echo floor(4.1);  // 4.0
echo floor(-4.1); // -5.0 (down = away from 0 for negatives)
echo floor(-4.9); // -5.0

// intval() / (int) — truncate (cut off, not round)
echo (int) 4.9;   // 4 (decimal cut off)
echo (int) -4.9;  // -4 (cut toward 0)

// Difference table for negative values
$value = -4.5;
echo round($value);    // -5.0 (round half away from zero)
echo ceil($value);     // -4.0 (up = toward 0)
echo floor($value);    // -5.0 (down = away from 0)
echo (int) $value;     // -4 (truncate toward 0)

Floating-Point Precision Traps #

<?php
// The famous IEEE 754 precision problem
var_dump(0.1 + 0.2 == 0.3);    // bool(false) — surprising!
var_dump(0.1 + 0.2);           // float(0.30000000000000004)

// This isn't a PHP bug — every language using IEEE 754 has this problem

// Solution 1: use an epsilon for float comparisons
function floatEqual(float $a, float $b, float $epsilon = PHP_FLOAT_EPSILON): bool
{
    return abs($a - $b) < $epsilon;
}

var_dump(floatEqual(0.1 + 0.2, 0.3)); // bool(true)

// Solution 2: round before comparing
var_dump(round(0.1 + 0.2, 10) === round(0.3, 10)); // bool(true)

// Solution 3: use BCMath for important calculations
echo bcadd('0.1', '0.2', 10); // "0.3000000000" — perfect precision

// A real case that often happens
$price = 19.99;
$qty   = 3;
echo $price * $qty;              // 59.97 — correct by coincidence
echo $price * $qty == 59.97;    // might be false!

// Safe: BCMath or integer cents
$priceCents = 1999;  // 19.99 in cents
$total      = $priceCents * $qty;   // 5997 cents = 59.97 — always exact

// PHP_FLOAT_EPSILON — the smallest value such that 1.0 + EPSILON !== 1.0
echo PHP_FLOAT_EPSILON; // 2.2204460492503E-16

Random Numbers #

PHP has two kinds of random functions: regular ones (for simulations/games) and cryptographic ones (for security tokens):

<?php
// rand() and mt_rand() — NOT for security purposes!
echo rand();           // random integer between 0 and getrandmax()
echo rand(1, 100);     // random integer between 1 and 100
echo mt_rand(1, 100);  // faster with a better distribution

// random_int() — cryptographic, for security purposes
// Use this for tokens, OTPs, password resets, etc.
echo random_int(100000, 999999); // a secure 6-digit OTP
echo random_int(1, PHP_INT_MAX); // a secure random integer

// random_bytes() — random bytes for tokens
$token = bin2hex(random_bytes(32)); // 64 hex characters
// Useful for: CSRF tokens, session tokens, password reset links

// Shuffle an array with good distribution
$cards = range(1, 52);
shuffle($cards); // in-place, uses a good RNG

// array_rand — pick random keys from an array
$fruits = ['apple', 'mango', 'orange', 'grape'];
$key    = array_rand($fruits);         // one random key
$two    = array_rand($fruits, 2);      // two random keys

// Random float from 0.0 to 1.0
echo mt_rand() / mt_getrandmax();    // float between 0.0 and 1.0

// Random float within a range
function randomFloat(float $min, float $max): float
{
    return $min + mt_rand() / mt_getrandmax() * ($max - $min);
}
echo randomFloat(1.5, 9.9); // float between 1.5 and 9.9

Trigonometry #

<?php
// All trig functions use RADIANS, not degrees
$degrees = 45;
$radians = deg2rad($degrees); // convert degrees to radians

echo sin(deg2rad(30));  // 0.5 — sin 30°
echo cos(deg2rad(60));  // 0.5 — cos 60°
echo tan(deg2rad(45));  // 1.0 — tan 45°

// Inverse
echo rad2deg(asin(0.5)); // 30.0 — arcsin(0.5) = 30°
echo rad2deg(acos(0.5)); // 60.0 — arccos(0.5) = 60°
echo rad2deg(atan(1.0)); // 45.0 — arctan(1) = 45°

// atan2 — two-argument arctan (more robust than atan)
$y = 1.0; $x = 1.0;
echo rad2deg(atan2($y, $x)); // 45.0

// Hyperbolic
echo sinh(1); // 1.1752011936438
echo cosh(1); // 1.5430806348152
echo tanh(1); // 0.76159415595576

// Calculate the distance between two coordinate points (Euclidean)
function euclideanDistance(float $x1, float $y1, float $x2, float $y2): float
{
    return sqrt(($x2 - $x1) ** 2 + ($y2 - $y1) ** 2);
}

// Calculate the distance between two GPS coordinates (Haversine formula)
function haversineDistance(float $lat1, float $lng1, float $lat2, float $lng2): float
{
    $r    = 6371; // Earth's radius in km
    $dLat = deg2rad($lat2 - $lat1);
    $dLng = deg2rad($lng2 - $lng1);
    $a    = sin($dLat / 2) ** 2
          + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * sin($dLng / 2) ** 2;
    return $r * 2 * atan2(sqrt($a), sqrt(1 - $a));
}

$jakarta   = [-6.2088, 106.8456];
$surabaya  = [-7.2575, 112.7521];
echo round(haversineDistance(...$jakarta, ...$surabaya)) . " km\n"; // ~666 km

Math Constants #

<?php
echo M_PI;        // 3.1415926535898  — π
echo M_E;         // 2.718281828459   — Euler's number (e)
echo M_LOG2E;     // 1.4426950408890  — log₂(e)
echo M_LOG10E;    // 0.43429448190325 — log₁₀(e)
echo M_LN2;       // 0.69314718055995 — ln(2)
echo M_LN10;      // 2.302585092994   — ln(10)
echo M_SQRT2;     // 1.4142135623731  — √2
echo M_SQRT3;     // 1.7320508075689  — √3
echo M_1_PI;      // 0.31830988618379 — 1/π
echo M_2_PI;      // 0.63661977236758 — 2/π
echo M_SQRT1_2;   // 0.70710678118655 — 1/√2

echo PHP_INT_MAX;      // 9223372036854775807
echo PHP_INT_MIN;      // -9223372036854775808
echo PHP_INT_SIZE;     // 8 (bytes)
echo PHP_FLOAT_MAX;    // 1.7976931348623E+308
echo PHP_FLOAT_MIN;    // 2.2250738585072E-308
echo PHP_FLOAT_EPSILON;// 2.2204460492503E-16
echo INF;              // INF
echo NAN;              // NAN
echo -INF;             // -INF

// Check special values
var_dump(is_finite(42.0));    // bool(true)
var_dump(is_infinite(INF));   // bool(true)
var_dump(is_nan(NAN));        // bool(true)
var_dump(is_nan(sqrt(-1)));   // bool(true)
var_dump(is_numeric("42"));   // bool(true)
var_dump(is_numeric("42.5")); // bool(true)
var_dump(is_numeric("0x1A")); // bool(false) in PHP 7+

BCMath — Arbitrary Precision #

BCMath (Binary Calculator) is a library for decimal calculations with arbitrarily selectable precision — a must for financial applications:

<?php
// Arguments are STRINGS for full precision
// The third argument is the number of decimals in the result

// Basic BCMath operations
echo bcadd('10.1', '20.2', 2);      // "30.30" — addition
echo bcsub('100.00', '30.75', 2);   // "69.25" — subtraction
echo bcmul('19.99', '3', 2);        // "59.97" — multiplication
echo bcdiv('100.00', '3', 10);      // "33.3333333333" — division
echo bcmod('100', '7');             // "2" — modulo (integers only)
echo bcpow('2', '32', 0);           // "4294967296" — powers
echo bcsqrt('2', 10);               // "1.4142135624" — square root

// bccomp — comparison (returns -1, 0, or 1)
echo bccomp('10.5', '10.50', 2);  // 0 — equal
echo bccomp('10.6', '10.5',  1);  // 1 — greater
echo bccomp('10.4', '10.5',  1);  // -1 — smaller

// Set the default precision
bcscale(4); // all subsequent BC operations use 4 decimals
echo bcadd('1', '2');  // "3.0000"

// Real example: discount and VAT calculations
function calculateTotal(string $unitPrice, string $qty, string $discount, string $vat = '0.11'): array
{
    $subtotal      = bcmul($unitPrice, $qty, 4);
    $deduction     = bcmul($subtotal, $discount, 4);
    $afterDiscount = bcsub($subtotal, $deduction, 4);
    $tax           = bcmul($afterDiscount, $vat, 4);
    $total         = bcadd($afterDiscount, $tax, 4);

    return [
        'subtotal'       => $subtotal,
        'deduction'      => $deduction,
        'after_discount' => $afterDiscount,
        'tax'            => $tax,
        'total'          => $total,
    ];
}

$result = calculateTotal('19999.99', '3', '0.10');
// subtotal: 59999.9700
// deduction: 5999.9970
// after_discount: 53999.9730
// tax: 5939.9970
// total: 59939.9700

// Format the output
function formatRupiah(string $value, int $decimals = 0): string
{
    // Convert the BC string to a float for number_format (safe because it's only for display)
    return 'Rp ' . number_format((float) $value, $decimals, ',', '.');
}
echo formatRupiah($result['total']); // "Rp 59.940"

GMP — Very Large Integers #

GMP (GNU Multiple Precision) handles integers beyond PHP_INT_MAX:

<?php
// Large factorials — PHP_INT_MAX isn't enough
function factorialGmp(int $n): \GMP
{
    $result = gmp_init(1);
    for ($i = 2; $i <= $n; $i++) {
        $result = gmp_mul($result, gmp_init($i));
    }
    return $result;
}

$factorial100 = factorialGmp(100);
echo gmp_strval($factorial100);
// 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000

// GMP operations
$a = gmp_init('123456789012345678901234567890');
$b = gmp_init('987654321098765432109876543210');

$sum      = gmp_add($a, $b);
$diff     = gmp_sub($b, $a);
$product  = gmp_mul($a, $b);
$quotient = gmp_div_q($b, $a); // quotient
$remainder = gmp_mod($b, $a);  // modulo

echo gmp_strval($sum);  // "1111111110111111111011111111100"

// Comparison
$cmp = gmp_cmp($a, $b);  // -1 (a < b)

// GMP is useful for cryptography — RSA and Diffie-Hellman
// Modular exponentiation (used in RSA)
$base  = gmp_init('2');
$exp   = gmp_init('100');
$mod   = gmp_init('1000000007'); // a large prime number
$result = gmp_powm($base, $exp, $mod); // (2^100) mod 1000000007
echo gmp_strval($result); // "976371285"

// GCD (Greatest Common Divisor)
$gcd = gmp_gcd(gmp_init(48), gmp_init(18));
echo gmp_strval($gcd); // "6"

// Check whether a number is prime
$p = gmp_init('104729'); // a prime number
echo gmp_prob_prime($p) > 0 ? "probably prime" : "definitely not prime";
// Value 0: not prime, 1: probably prime, 2: definitely prime

Correct Financial Calculation Patterns #

<?php
// ANTI-PATTERN: using floats for money
$price    = 19.99;
$qty      = 100;
$total    = $price * $qty; // might not be exactly 1999.00
$vat      = $total * 0.11;
$totalVat = $total + $vat; // might have small precision errors

// CORRECT Option 1: Store in the smallest unit (cents/whole rupiah)
$priceCents = 1999; // 19.99 in cents
$qty        = 100;
$total      = $priceCents * $qty;  // 199900 cents = 1999.00
$vat        = intdiv($total * 11, 100); // 21989 cents = 219.89
$totalVat   = $total + $vat;       // 221889 cents = 2218.89

// Display
echo number_format($totalVat / 100, 2, ',', '.'); // "2.218,89"

// CORRECT Option 2: BCMath for calculations
$price    = '19.99';
$qty      = '100';
$total    = bcmul($price, $qty, 4);    // "1999.0000"
$vat      = bcmul($total, '0.11', 4); // "219.8900"
$totalVat = bcadd($total, $vat, 2);   // "2218.89"

echo 'Rp ' . number_format((float) $totalVat, 2, ',', '.'); // "Rp 2.218,89"

// Financial rounding — PHP_ROUND_HALF_UP for consistency
$value    = 2218.885;
$rounded  = round($value, 2, PHP_ROUND_HALF_UP); // 2218.89
$bcRounded = bcadd(bcsub(bcadd($value, '0.005', 3), '0', 2), '0', 2); // via BCMath

Summary #

  • 0.1 + 0.2 !== 0.3 — this isn’t a bug, it’s the nature of IEEE 754. Use an epsilon (PHP_FLOAT_EPSILON) for float comparisons, or avoid floats for important values.
  • BCMath for financebcadd, bcsub, bcmul, bcdiv with string arguments give exact decimal precision without floating-point errors.
  • Store money as integers (cents/whole rupiah without decimals) — $price = 1999 (not 19.99) is the safest and most efficient approach for monetary calculations.
  • random_int() and random_bytes() for security — never use rand() or mt_rand() for tokens, OTPs, or password resets. Neither is cryptographically secure.
  • round() has four modesPHP_ROUND_HALF_EVEN (banker’s rounding) reduces accumulation bias in calculations involving many roundings.
  • All trigonometric functions use radians — use deg2rad() and rad2deg() for conversion. atan2($y, $x) is more robust than atan($y/$x) because it handles all quadrants.
  • GMP for numbers above PHP_INT_MAX — cryptography, large factorials, and number-theory operations need precision beyond 64-bit integers.
  • intdiv() for safe integer division (PHP 7+) — it doesn’t produce a float like the / operator, and its intent is clearer than (int)($a / $b).

← Previous: IO   Next: Filter & Validation →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact