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
- Use +, -, *, /, %, ** and intdiv correctly
- Round with round, ceil, and floor
- Format money and big numbers with number_format
- Reach for max, min, sqrt, pow and abs
- Generate secure random numbers with random_int
- Avoid the 0.1 + 0.2 floating-point trap
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)
- A price comparison fails by a fraction of a cent — you compared floats with ===. Use an epsilon tolerance, integer cents, or bcmath.
- Integer division returns a decimal — / always returns a float. Use intdiv($a, $b) or floor($a / $b).
- "Division by zero" / DivisionByZeroError — modulo or intdiv by 0 throws. Guard with if ($b !== 0) first.
- number_format result can't be added to — it returns a string with commas. Format only at display time; calculate with the raw number.
Pro Tips
- 💡 Store money as integer cents. Hold 1999 (cents) rather than 19.99 to dodge float errors entirely.
- 💡 Use random_int() for tokens and shuffles — it's secure; rand() is predictable.
- 💡 $n % 2 === 0 tests for even. Modulo is the quickest even/odd and "every Nth" check.
📋 Quick Reference — Numbers & Math
| Operator / Fn | Example | Result |
|---|---|---|
| % | 17 % 5 | 2 (remainder) |
| ** | 2 ** 10 | 1024 |
| intdiv | intdiv(17,5) | 3 |
| round | round(3.14159,2) | 3.14 |
| ceil / floor | ceil(3.1) | 4 |
| number_format | number_format(1000,2) | 1,000.00 |
| max / min | max(3,9) | 9 |
| random_int | random_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!
- ✅ % gives a remainder, ** raises a power, intdiv divides whole
- ✅ round, ceil, and floor control rounding direction
- ✅ number_format makes money and big numbers readable
- ✅ Use random_int for secure randomness
- ✅ Never compare floats with ===; use cents or an epsilon
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
- Previous: Strings & String Functions
- Next: Type Juggling & Casting — How PHP converts types automatically and when to cast explicitly
- Quick reference: PHP cheat sheet
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.