Numbers & Math

Reviewed & published by Brayan K

From a shopping cart total to a dice roll, numbers are everywhere. This lesson covers arithmetic and modulo, rounding, money-friendly formatting, the built-in math functions, secure randomness, and the one floating-point trap you absolutely must know.

Part of the free PHP course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn in This Lesson

1️⃣ Arithmetic & Modulo

PHP has the usual operators plus two you'll lean on: modulo (%) gives the remainder of a division — perfect for "is this even?" or wrapping around a cycle — and the exponent operator (**). Remember the rule from Data Types: / always returns a float, so use intdiv() for whole-number division.

<?php
$a = 17;
$b = 5;

echo $a + $b . "\n";   // 22
echo $a - $b . "\n";   // 12
echo $a * $b . "\n";   // 85
echo $a / $b . "\n";   // 3.4   — / always gives a float
echo $a % $b . "\n";   // 2     — modulo: the remainder
echo $a ** $b . "\n";  // 1419857 — exponent: 17 to the 5th power

// intdiv() does true integer division (no remainder, no float).
echo intdiv($a, $b) . "\n";  // 3
?>

2️⃣ Rounding

Three functions cover almost every need. round() goes to the nearest value (with optional decimal precision), ceil() always rounds up, and floor() always rounds down. abs() removes the sign.

<?php
$pi = 3.14159;

echo round($pi, 2) . "\n";   // 3.14  — round to 2 decimals
echo round($pi) . "\n";      // 3     — nearest whole number
echo ceil($pi) . "\n";       // 4     — always round UP
echo floor($pi) . "\n";      // 3     — always round DOWN

// round() uses "round half away from zero" by default.
echo round(2.5) . "\n";      // 3
echo round(3.5) . "\n";      // 4
echo round(-2.5) . "\n";     // -3

// abs() strips the sign.
echo abs(-42) . "\n";        // 42
?>

3️⃣ Formatting Numbers

Raw numbers look unfriendly to humans. number_format() adds thousands separators and a fixed number of decimals — the go-to for prices, statistics, and dashboards. You can even swap the separators for international formats.

<?php
// number_format() makes numbers human-friendly: decimals + thousands.
$total = 1234567.891;

echo number_format($total) . "\n";              // 1,234,568
echo number_format($total, 2) . "\n";           // 1,234,567.89
echo number_format($total, 2, ".", " ") . "\n"; // 1 234 567.89 (custom separators)

// Build a price string with a currency symbol.
$price = 49.5;
echo "$" . number_format($price, 2) . "\n";     // $49.50
?>

4️⃣ The Math Library

PHP bundles a complete set of math helpers: max/min, sqrt, pow, constants like M_PI and PHP_INT_MAX, and — crucially for anything security-related — random_int(), which is cryptographically secure.

<?php
// PHP ships a full math library.
echo max(3, 9, 2) . "\n";      // 9
echo min(3, 9, 2) . "\n";      // 2
echo sqrt(144) . "\n";         // 12
echo pow(2, 10) . "\n";        // 1024
echo PHP_INT_MAX . "\n";       // 9223372036854775807 (on 64-bit)

// random_int() is cryptographically secure — use it, not rand().
$dice = random_int(1, 6);
echo "You rolled between 1 and 6\n";

// Constants you'll meet often.
echo M_PI . "\n";              // 3.1415926535898
?>

5️⃣ The Floating-Point Trap ⚠️

This is the single most important thing in the lesson. Because floats are stored in binary, simple decimals like 0.1 can't be represented exactly, so 0.1 + 0.2 is not exactly 0.3. Never compare floats with ===. Compare within a tiny tolerance, and for money work in integer cents or use bcmath.

<?php
// THE classic floating-point trap — true in every language, not just PHP.
var_dump(0.1 + 0.2 === 0.3);   // bool(false) !!

// Why? 0.1 + 0.2 is actually 0.30000000000000004 in binary float.
printf("%.17f\n", 0.1 + 0.2);  // 0.30000000000000004

// The fix: compare within a tiny tolerance (epsilon), never with ===.
$diff = abs((0.1 + 0.2) - 0.3);
var_dump($diff < PHP_FLOAT_EPSILON);   // bool(true)

// For money, work in integer cents OR use the bcmath functions.
echo bcadd("0.1", "0.2", 1) . "\n";    // 0.3  (exact, string-based)
?>

Now you try. Fill the ___ blanks to total a small cart and format the result as money.

<?php
// 🎯 YOUR TURN — fill each ___ to total a shopping cart.
$prices = [12.5, 7.25, 3.0];

$sum = ___($prices);              // 👉 array_sum() the prices
$rounded = ___($sum, 2);          // 👉 round() to 2 decimals
$display = "$" . ___($rounded, 2);// 👉 number_format() with 2 decimals

echo $sum . "\n";                // 22.75
echo $rounded . "\n";            // 22.75
echo $display . "\n";            // $22.75

// ✅ Expected output:
//    22.75
//    22.75
//    $22.75
?>

🧩 Reorder Challenge

These scrambled lines should compute an average and print it rounded to one decimal. Put them in the order that produces Average: 7.7.

First create the array, then sum it, then divide the sum by the count to get the average, and only then round and print. Each line depends on the variable produced by the line before it. The exact average is 7.666…, which round($avg, 1) turns into 7.7.

🧠 Quick Recall

Predict the output before revealing each answer.

Prints 2. Modulo returns the remainder: 17 divided by 5 is 3 with 2 left over.

Prints bool(false). Binary floating point can't represent 0.1 or 0.2 exactly, so the sum is 0.30000000000000004. Compare floats with an epsilon, never with ===.

Prints 4 3. ceil always rounds up to 4; floor always rounds down to 3.

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Numbers & Math

Operator / FnExampleResult
%17 % 52 (remainder)
**2 ** 101024
intdivintdiv(17,5)3
roundround(3.14159,2)3.14
ceil / floorceil(3.1)4
number_formatnumber_format(1000,2)1,000.00
max / minmax(3,9)9
random_intrandom_int(1,6)secure 1–6

Mini-Challenge: Invoice Line

Write it yourself, run it on onecompiler.com/php or your own machine, then check against the expected output in the comments.

<?php
// 🎯 MINI-CHALLENGE: an invoice line with tax.
// No code filled in — follow the steps, then run and compare.
//
// 1. const VAT = 0.20.
// 2. $qty = 3, $unit = 19.99.
// 3. $subtotal = $qty * $unit.
// 4. $tax = $subtotal * VAT.
// 5. $total = $subtotal + $tax.
// 6. echo three lines, each money formatted to 2 decimals with number_format:
//       Subtotal: $<subtotal>
//       VAT 20%:  $<tax>
//       Total:    $<total>
//
// ✅ Expected output:
//    Subtotal: $59.97
//    VAT 20%:  $11.99
//    Total:    $71.96

// your code here
?>

🎉 Lesson Complete!

Practice quiz

What does echo 17 % 5; print?

  • 3
  • 3.4
  • 2
  • 12

Answer: 2. Modulo returns the remainder: 17 divided by 5 is 3 with 2 left over.

What does echo 17 / 5; print?

  • 3.4
  • 3
  • 2
  • 4

Answer: 3.4. The / operator always returns a float, so 17 / 5 is 3.4. Use intdiv() for whole-number division.

What does var_dump(0.1 + 0.2 === 0.3); output?

  • bool(true)
  • float(0.3)
  • An error
  • bool(false)

Answer: bool(false). Binary floats can't represent 0.1 or 0.2 exactly, so the sum is 0.30000000000000004. Never compare floats with ===.

What does echo round(2.5); print?

  • 2
  • 3
  • 2.5
  • 4

Answer: 3. round() defaults to 'round half away from zero', so 2.5 becomes 3.

What does echo ceil(3.1) . ' ' . floor(3.9); print?

  • 4 3
  • 3 4
  • 4 4
  • 3 3

Answer: 4 3. ceil always rounds up (3.1 -> 4); floor always rounds down (3.9 -> 3).

What does echo number_format(1234567.891, 2); print?

  • 1234567.89
  • 1.234.567,89
  • 1,234,567.89
  • 1,234,568

Answer: 1,234,567.89. number_format with 2 decimals adds comma thousands separators: 1,234,567.89.

What does echo intdiv(17, 5); print?

  • 3.4
  • 3
  • 2
  • 4

Answer: 3. intdiv does true integer division — no remainder, no float — so 17 intdiv 5 is 3.

Which function should you use for cryptographically secure random numbers?

  • rand()
  • mt_rand()
  • round()
  • random_int()

Answer: random_int(). random_int() is cryptographically secure and uniformly distributed; rand() and mt_rand() are predictable.

What does echo 2 ** 10; print?

  • 20
  • 1024
  • 100
  • 1000

Answer: 1024. ** is the exponent operator: 2 to the 10th power is 1024.

What does the result of number_format() return that affects further math?

  • A float you can keep calculating with
  • An integer
  • A string with separators — format only at display time
  • An array of digits

Answer: A string with separators — format only at display time. number_format returns a string with commas, so use it only at display time; calculate with the raw number.

Continue this course

Frequently asked questions

Why is 0.1 + 0.2 not exactly 0.3 in PHP?

Floats are stored in binary, and many decimal fractions — including 0.1 and 0.2 — have no exact binary representation, so tiny rounding errors creep in. The sum is actually 0.30000000000000004. This is not a PHP bug; it's true of floating-point arithmetic everywhere. Never compare floats with ===. Instead compare the absolute difference against a small epsilon (PHP_FLOAT_EPSILON), and for money use integer cents or the bcmath/gmp extensions for exact decimal math.

What is the difference between round(), ceil(), and floor()?

round() goes to the nearest value, with an optional precision: round(3.14159, 2) is 3.14. By default it rounds halves away from zero, so round(2.5) is 3. ceil() always rounds up to the next integer (ceil(3.1) is 4), and floor() always rounds down (floor(3.9) is 3). Use round() for display values, ceil() when you must not under-provision (e.g. number of pages), and floor() when you must not over-count.

How do I format a number with commas and decimal places?

Use number_format(). number_format(1234567.891, 2) gives "1,234,567.89" — two decimals and comma thousands separators. You can customise the separators: number_format($n, 2, ",", ".") produces the European "1.234.567,89". It returns a string, so it's for display, not further calculation. Wrap it with a currency symbol like "$" . number_format($price, 2) for prices.

Should I use rand() or random_int() for random numbers?

Use random_int() for anything that matters — tokens, passwords, shuffles, game logic — because it is cryptographically secure and uniformly distributed. rand() and mt_rand() are faster but predictable and unsuitable for security. random_int(1, 6) returns a fair integer between 1 and 6 inclusive. For random floats or array picks, build on random_int rather than the legacy generators.

Related lessons