Senior 5 min · March 06, 2026

PHP Math Functions — mt_rand() Caused Revenue Loss

A sudden spike in high-value discounts on new accounts traced to mt_rand().

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • PHP math functions are one-liner replacements for manual arithmetic logic
  • abs(), max(), min() handle absolute value and clamping
  • round(), ceil(), floor() control rounding direction – choose wrong and you'll lose money or items
  • fmod() is the only safe modulo for floats; % silently truncates to integers
  • rand()/mt_rand() are predictable – use random_int() for security-sensitive values
  • pow() and sqrt() power real features: compound interest, distance, pagination
Plain-English First

Think of PHP's math functions as a calculator app built right into the language. Just like your phone's calculator has buttons for square roots, rounding, and random numbers, PHP has ready-made functions you can call instead of writing the logic yourself. You don't need to know the math behind finding a square root — you just press the button (call the function) and PHP hands you the answer. That's the whole idea.

Every real web application does math. An e-commerce site rounds product prices to two decimal places. A lottery widget picks random numbers. A fitness tracker calculates a user's BMI. If you had to write all that arithmetic logic from scratch every time, you'd spend more time reinventing the wheel than building actual features. PHP's built-in math functions exist to solve exactly that problem — they give you a toolkit of battle-tested, one-line solutions for the most common numerical tasks you'll ever face as a web developer.

The deeper problem these functions solve is precision and safety. Raw PHP arithmetic (+, -, *, /) gets you far, but it has gaps. What happens when you need a number that's 'never lower than zero'? Or when you want a random discount code that isn't predictable? Or when a division result has 14 decimal places and you need exactly 2? Plain operators can't handle these scenarios gracefully — but PHP's math functions can, and they've been optimised over decades so you don't have to worry about edge cases.

By the end of this article you'll be able to: round prices correctly for shopping carts, generate random numbers safely, work with powers and square roots, clamp values with abs(), and know exactly which rounding function to reach for in any situation. You'll also know three mistakes that trip up almost every beginner — and how to dodge them.

The Building Blocks — abs(), max(), min() and fmod()

Before we touch anything fancy, let's cover the four functions you'll reach for most often in everyday code. Think of these as the Swiss Army knife of PHP math.

abs() returns the absolute value of a number — meaning it strips away any negative sign. Imagine a bank statement: you owe $50, which is -50 in your account. abs(-50) gives you 50, which is the amount, regardless of direction. You use this whenever you care about distance or magnitude, not direction.

max() and min() return the largest or smallest value from a list. These are incredibly useful for clamping values — for example, making sure a discount never exceeds 100% or a quantity never drops below 1.

fmod() is the floating-point version of the % (modulo) operator. The % operator only works cleanly with integers. If you try to find the remainder when dividing 10.5 by 3.2 using %, PHP silently converts them to integers and gives you the wrong answer. fmod() handles decimal remainders properly — use it whenever your numbers aren't whole.

building_blocks.phpPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?php

// --- abs(): Get the magnitude, ignore the sign ---
$temperatureDrop = -12.5; // temperature fell by 12.5 degrees
$degreesChanged  = abs($temperatureDrop); // we want 'how much', not 'which direction'
echo "Temperature changed by: " . $degreesChanged . " degrees\n"; // 12.5

// --- max() and min(): Clamp values to safe limits ---
$userRequestedDiscount = 150; // a cheeky user tried to claim 150% off
$maximumAllowedDiscount = 100;

// min() picks the SMALLER of the two — so it acts as a ceiling
$appliedDiscount = min($userRequestedDiscount, $maximumAllowedDiscount);
echo "Applied discount: " . $appliedDiscount . "%\n"; // 100 — cannot exceed 100%

$itemsInCart = 0;
$minimumQuantity = 1;

// max() picks the LARGER of the two — so it acts as a floor
$safeQuantity = max($itemsInCart, $minimumQuantity);
echo "Safe quantity: " . $safeQuantity . "\n"; // 1 — cannot go below 1

// --- max() and min() also work across arrays ---
$productPrices = [9.99, 24.50, 4.75, 199.00, 12.00];
echo "Cheapest product: $" . min($productPrices) . "\n"; // 4.75
echo "Most expensive: $"  . max($productPrices) . "\n"; // 199

// --- fmod(): Floating-point modulo ---
$totalDistance   = 10.5; // kilometres
$lapLength       = 3.2;  // kilometres per lap
$remainingAfterFullLaps = fmod($totalDistance, $lapLength);
echo "Distance left after full laps: " . $remainingAfterFullLaps . " km\n"; // 0.9

// Compare: using % on floats gives wrong result
$wrongRemainder = 10.5 % 3.2; // PHP silently converts to 10 % 3
echo "Wrong remainder using %%: " . $wrongRemainder . "\n"; // 1 — incorrect!

?>
Output
Temperature changed by: 12.5 degrees
Applied discount: 100%
Safe quantity: 1
Cheapest product: $4.75
Most expensive: $199
Distance left after full laps: 0.9 km
Wrong remainder using %: 1
Watch Out: % vs fmod()
Using the % operator on floats is a silent bug — PHP converts both values to integers before calculating, giving you a completely wrong remainder with no error message. Always use fmod() when either number has a decimal point.
Production Insight
abs() is commonly used to normalise values in aggregations — e.g., calculating total change regardless of direction.
max() and min() are cheap: O(n) for arrays, O(1) for two arguments.
Production trap: max() on an empty array returns false, not 0 — always check array emptiness first.
Key Takeaway
abs() strips sign, max()/min() clamp values, fmod() handles floats.
Never use % on floats — it's a silent truncation bug.
Check array emptiness before calling max()/min() on arrays.

Rounding Numbers — round(), ceil() and floor() for Real-World Prices

Rounding is where most beginners get confused because PHP gives you three different functions for it — and choosing the wrong one will cost your users money or break your UI.

Here's the mental model: imagine you're standing on a staircase at step 7.6. round() asks 'which step are you closer to?' — you're closer to 8, so it picks 8. ceil() (ceiling) always goes UP — you're above step 7, so it goes to 8. floor() always goes DOWN — you're below step 8, so it stays at 7.

round() is your default for prices and displaying data. It takes an optional second argument for decimal precision — round(9.999, 2) gives you 10.00. This is what you use for a final invoice total.

ceil() is what you use when partial units still cost a full unit. Shipping calculators love this — if a parcel weighs 2.1 kg and you're charged per full kg, you owe for 3 kg, not 2. ceil(2.1) gives 3.

floor() is for things that only count when complete. A user watched 4.8 episodes — they completed 4. floor(4.8) gives 4. Also commonly used in pagination calculations.

rounding_functions.phpPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?php

// --- round(): Standard rounding for prices and display ---
$rawPrice        = 19.9867452; // price after tax calculation
$displayPrice    = round($rawPrice, 2); // round to 2 decimal places
echo "Display price: $" . $displayPrice . "\n"; // 19.99

$halfwayValue    = 2.5;
$roundedHalfway  = round($halfwayValue); // PHP rounds .5 UP by default
echo "Rounded 2.5: " . $roundedHalfway . "\n"; // 3

// round() also rounds to tens, hundreds etc using negative precision
$roughEstimate   = 4873;
$roundedToNearest100 = round($roughEstimate, -2); // -2 means round to hundreds
echo "Nearest hundred: " . $roundedToNearest100 . "\n"; // 4900

// --- ceil(): Always round UP — for conservative estimates ---
$parcelWeightKg    = 2.1;   // parcel weighs 2.1 kg
$billableKg        = ceil($parcelWeightKg); // shipping charges for whole kg units
echo "Billable kg: " . $billableKg . "\n"; // 3 — you pay for 3 full kg

$hoursWorked       = 3.25;  // worked 3 hours and 15 minutes
$billableHours     = ceil($hoursWorked); // consultants often bill full hours
echo "Billable hours: " . $billableHours . "\n"; // 4

// --- floor(): Always round DOWN — for completed units ---
$episodesWatched   = 4.8; // user stopped mid-episode
$completedEpisodes = floor($episodesWatched); // only count finished episodes
echo "Completed episodes: " . $completedEpisodes . "\n"; // 4

// Practical pagination: how many full pages of 10 items fit in 47 results?
$totalResults      = 47;
$resultsPerPage    = 10;
$totalPages        = ceil($totalResults / $resultsPerPage); // always round UP for pages
echo "Total pages: " . $totalPages . "\n"; // 5 (page 5 has only 7 items)

$completePagesOnly = floor($totalResults / $resultsPerPage); // full pages only
echo "Full pages: " . $completePagesOnly . "\n"; // 4

?>
Output
Display price: $19.99
Rounded 2.5: 3
Nearest hundred: 4900
Billable kg: 3
Billable hours: 4
Completed episodes: 4
Total pages: 5
Full pages: 4
Pro Tip: Pagination always uses ceil()
For pagination, always use ceil(total / perPage) — never round(). If you have 11 items and show 10 per page, round() gives you 1 page and your last item vanishes. ceil() correctly gives you 2 pages.
Production Insight
Using round() for pagination is the #1 math bug in e-commerce production — it silently truncates the last page.
ceil() is also used in pricing for tiered shipping: ceil($weight / $perKg) * $rate.
Always test with boundary values: 0 items, exactly N items per page, N+1 items.
Key Takeaway
round() for display, ceil() for conservative estimates, floor() for completed units.
Pagination must use ceil() — never round().
Know the staircase analogy: round() to closest step, ceil() up, floor() down.

Power, Square Root and Logarithms — pow(), sqrt() and log()

These functions feel intimidating if you haven't touched algebra in a while, but their real-world uses are surprisingly practical — and you don't need to love math to use them.

pow($base, $exponent) raises a number to a power. Think of compound interest: if you invest $1,000 at 5% annual interest, after 10 years you have 1000 * pow(1.05, 10). That's not hypothetical — financial tools, loan calculators, and subscription revenue projections all use pow().

sqrt() gives you the square root. Beyond geometry, it's used in distance calculations. The straight-line distance between two points on a map uses a square root under the hood (the Pythagorean theorem). Recommendation engines and search ranking algorithms use it too.

log() is the natural logarithm. This one's more advanced, but you'll encounter it in data normalisation, audio volume scaling (decibels are logarithmic), and analytics dashboards. log($number, $base) lets you specify a custom base — log(1000, 10) returns 3 because 10³ = 1000.

The key insight: these aren't just academic functions — they power real features in production apps every day.

powers_and_roots.phpPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?php

// --- pow(): Raise a number to a power ---
// Compound interest formula: A = P * (1 + r)^t
$principal       = 1000.00; // initial investment in dollars
$annualRate      = 0.05;    // 5% annual interest rate
$years           = 10;      // investment period

$futureValue     = $principal * pow(1 + $annualRate, $years);
echo "Future value after 10 years: $" . round($futureValue, 2) . "\n"; // $1628.89

// Squaring a number is just pow($number, 2)
$sideLength      = 7; // side of a square in metres
$areaOfSquare    = pow($sideLength, 2); // same as 7 * 7
echo "Area of square: " . $areaOfSquare . " sq metres\n"; // 49

// PHP also supports the ** operator as a shortcut for pow()
$cubeVolume      = 4 ** 3; // same as pow(4, 3)
echo "Volume of cube: " . $cubeVolume . " cubic units\n"; // 64

// --- sqrt(): Square root ---
// Straight-line distance between two map points (Pythagorean theorem)
$horizontalDistance = 3.0; // km east
$verticalDistance   = 4.0; // km north

// distance = sqrt(horizontal^2 + vertical^2)
$straightLineDistance = sqrt(pow($horizontalDistance, 2) + pow($verticalDistance, 2));
echo "Straight-line distance: " . $straightLineDistance . " km\n"; // 5 (classic 3-4-5 triangle)

$area            = 144; // area of a square in cm^2
$sideFromArea    = sqrt($area); // reverse-calculate the side length
echo "Side length: " . $sideFromArea . " cm\n"; // 12

// --- log(): Logarithm ---
$number          = 1000;
$base10Log       = log($number, 10); // log base 10 of 1000 = 3 (because 10^3 = 1000)
echo "log10(1000): " . $base10Log . "\n"; // 3

$naturalLog      = log(M_E); // M_E is PHP's built-in constant for Euler's number (~2.718)
echo "Natural log of e: " . $naturalLog . "\n"; // 1 (because ln(e) always = 1)

?>
Output
Future value after 10 years: $1628.89
Area of square: 49 sq metres
Volume of cube: 64 cubic units
Straight-line distance: 5 km
Side length: 12 cm
log10(1000): 3
Natural log of e: 1
Interview Gold: ** vs pow()
Since PHP 5.6, you can use the operator instead of pow()2 8 equals 256. Both are valid, but is more readable for simple cases. Interviewers love asking which is more 'modern' — it's .
Production Insight
pow() with large exponents can overflow floats — PHP returns INF beyond ~1e308.
sqrt() of negative numbers results in NAN — always validate input if domain uncertainty.
log() with large base (e.g., log(1e50, 10)) can lose precision due to float representation.
Key Takeaway
Use ** for simple exponentiation, pow() for base/exponent variables.
sqrt() is NAN for negative inputs — guard against it.
log($num, $base) returns the exponent needed to raise $base to get $num.

Random Numbers Done Right — rand(), mt_rand() and random_int()

PHP gives you three ways to generate random numbers, and picking the wrong one is a genuine security risk, not just bad practice. Let's break down the difference clearly.

rand($min, $max) is the old way — it's fast but uses a weak algorithm that's predictable if someone studies enough outputs. Never use this for anything security-related.

mt_rand($min, $max) uses the Mersenne Twister algorithm — much better statistical randomness and about 4x faster than old rand(). It's fine for non-security uses like shuffling a quiz order, picking a random featured article, or generating test data.

random_int($min, $max) is the one you should default to in modern PHP (7.0+). It uses cryptographically secure random number generation from your operating system. Use this for anything involving security: password reset tokens, lottery draws, discount codes, session IDs, OTPs. It's slightly slower but the difference is negligible for normal use.

The golden rule: if the random number protects something, use random_int(). If it's just for fun or display, mt_rand() is fine.

random_numbers.phpPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?php

// --- rand(): Old approach — avoid for anything important ---
$oldRandomNumber = rand(1, 100);
echo "Old rand (avoid for security): " . $oldRandomNumber . "\n"; // e.g. 73

// --- mt_rand(): Good for non-security randomness ---
// Pick a random quiz question index from a pool of 50 questions
$totalQuestions  = 50;
$randomQuestionIndex = mt_rand(0, $totalQuestions - 1); // arrays are 0-indexed
echo "Random quiz question index: " . $randomQuestionIndex . "\n"; // e.g. 31

// Generate a random RGB colour for a UI element
$red   = mt_rand(0, 255);
$green = mt_rand(0, 255);
$blue  = mt_rand(0, 255);
echo "Random colour: rgb($red, $green, $blue)\n"; // e.g. rgb(142, 87, 210)

// --- random_int(): Use this for SECURITY-SENSITIVE randomness ---
// Generate a 6-digit one-time password (OTP)
$otpCode = random_int(100000, 999999); // always 6 digits, never starts with 0
echo "Your OTP: " . $otpCode . "\n"; // e.g. 847291

// Generate a random discount code using random_int for the numeric part
$discountNumber = random_int(1000, 9999);
$discountCode   = "SAVE-" . $discountNumber;
echo "Discount code: " . $discountCode . "\n"; // e.g. SAVE-6183

// Simulate a fair dice roll
$diceRoll = random_int(1, 6);
echo "Dice rolled: " . $diceRoll . "\n"; // 1 through 6, evenly distributed

// --- Useful companion: shuffle() randomises an array ---
$lotteryNumbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
shuffle($lotteryNumbers); // randomises in place
$pickedNumbers  = array_slice($lotteryNumbers, 0, 3); // take first 3 after shuffle
echo "Lottery pick: " . implode(", ", $pickedNumbers) . "\n"; // e.g. 7, 2, 9

?>
Output
Old rand (avoid for security): 73
Random quiz question index: 31
Random colour: rgb(142, 87, 210)
Your OTP: 847291
Discount code: SAVE-6183
Dice rolled: 4
Lottery pick: 7, 2, 9
Watch Out: rand() is NOT cryptographically secure
Using rand() or mt_rand() to generate password reset tokens or session IDs is a real vulnerability. An attacker who observes enough outputs can predict future values. Always use random_int() for anything security-related — it's available in PHP 7.0+ and there's no good excuse not to.
Production Insight
random_int() throws an Exception if the system cannot generate secure random bytes — catch this and fall back to a queue or fail open.
mt_rand() is not suitable for cryptographic nonces — use random_bytes() for binary tokens.
For high-entropy tokens (e.g., 128-bit), combine random_bytes() with bin2hex() — never rely on mt_rand() alone.
Key Takeaway
rand() is obsolete, mt_rand() is okay for non-security, random_int() is the only safe choice for secure values.
random_int() is available in PHP 7.0+ — required for OTPs, tokens, passwords.
shuffle() uses mt_rand() internally — don't use it for cryptocurrency shuffling.

Real-World Patterns: Combining Math Functions for Business Logic

In production, you rarely use these functions in isolation. The power comes from combining them to solve real business problems.

Price display with precision control: round() alone is not enough for accurate tax calculations. You need to first round intermediate results, then apply rounding mode. Use round() with PHP_ROUND_HALF_UP everywhere in financial contexts.

Clamping with abs() and min()/max(): When a user enters a negative discount percentage, use abs() to normalise it. Then use min() to cap at maximum allowed, and return the value.

Fair random distribution with weighted logic: Use random_int(1, 100) combined with cumulative thresholds to implement weighted luck — e.g., 10% chance of a bonus.

Geolocation distance: Combine pow() and sqrt() to compute Haversine distance between two latitude/longitude points. Use rad2deg() and deg2rad() for conversions.

Pagination with boundary checks: Use ceil() for page count, max(1, ...) to ensure minimum page 1, and min() to cap at the last page. Then use round() for display count on the final page. Always test edge cases: 0 items, 1 item, exactly N items.

real_world_patterns.phpPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?php

// --- Price clamping with abs() and min() ---
$discountPercent = -20; // user input, could be negative
$normalized = abs($discountPercent); // 20
$maxAllowed = 30;
$finalDiscount = min($normalized, $maxAllowed); // 20 (but never above 30)
echo "Final discount: $finalDiscount%\n"; // 20

// --- Weighted random selection ---
$thresholds = [
    'bronze' => 50,  // 50% chance
    'silver' => 80,  // 30% chance
    'gold'   => 95,  // 15% chance
    'platinum' => 100 // 5% chance
];
$roll = random_int(1, 100);
foreach ($thresholds as $tier => $max) {
    if ($roll <= $max) {
        echo "You got {$tier}!\n";
        break;
    }
}

// --- Haversine distance between two coordinates ---
$lat1 = 40.7128; $lon1 = -74.0060; // New York
$lat2 = 34.0522; $lon2 = -118.2437; // Los Angeles
$earthRadius = 6371; // km
$dLat = deg2rad($lat2 - $lat1);
$dLon = deg2rad($lon2 - $lon1);
$a = sin($dLat/2)**2 + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * sin($dLon/2)**2;
$c = 2 * asin(sqrt($a));
$distance = round($c * $earthRadius, 2);
echo "Distance: $distance km\n"; // ~3935 km

// --- Safe pagination with all checks ---
$totalItems = 47;
$perPage = 10;
$currentPage = max(1, min(ceil($totalItems / $perPage), 5)); // request page 5, but limit to last page
$lastPage = max(1, ceil($totalItems / $perPage));
echo "Showing page $currentPage of $lastPage\n"; // Page 5 of 5

?>
Output
Final discount: 20%
You got silver!
Distance: 3935.11 km
Showing page 5 of 5
Composition Mindset
  • abs() normalises direction — use before min()/max() for clamping
  • round() with mode for financial precision
  • random_int() for fair draws, pow()/sqrt() for geo-distance
  • ceil() + max() + min() creates pagination with guardrails
Production Insight
Combining functions without understanding their type signatures can produce unexpected results.
Example: max(array_map('abs', $negatives)) works, but abs(max($negatives)) gives the negative value closest to zero, not the furthest.
Always test composite functions with edge cases: zero, negative, float, boundary values.
For financial logic, use number_format() with round() to avoid display rounding errors like 0.1 + 0.2 = 0.30000000000000004.
Key Takeaway
Compose functions intentionally — each step changes the number's meaning.
Always test edge cases for each math function individually before combining.
Financial rounding requires PHP_ROUND_HALF_UP and explicit precision.
● Production incidentPOST-MORTEMseverity: high

Lost Revenue from Random Discount Codes

Symptom
A sudden spike in high-value discounts triggered on accounts that had never purchased before — all within the same hour window.
Assumption
mt_rand() provided 'random enough' numbers for discount codes that were one-time use and time-limited.
Root cause
mt_rand() uses the Mersenne Twister PRNG, which is deterministic. An attacker who obtained a few consecutive codes could reverse-engineer the seed and predict every future code.
Fix
Switched the code generation from mt_rand(10000, 99999) to random_int(10000, 99999). Also added HMAC-based tokenisation to prevent replay attacks.
Key lesson
  • Never use mt_rand() or rand() for values that control access or carry monetary value.
  • random_int() is available since PHP 7.0 and costs negligible performance overhead.
  • If you need a human-readable code, combine random_int() with a checksum digit to prevent typo-generated invalid codes from being used.
Production debug guideDiagnose rounding, modulo, and random-number bugs before they cost you money.4 entries
Symptom · 01
Pricing total is $0.01 off on specific products (e.g., $9.99 tax calculated as $0.83 instead of $0.84)
Fix
Check if round() was used with default rounding mode. Use round($value, 2, PHP_ROUND_HALF_UP) to enforce consistent behaviour. Also verify that PHP's precision ini directive is set to 14.
Symptom · 02
Pagination page count is wrong: last page never appears when total items is not a multiple of per-page
Fix
Search for round() in pagination code. Replace with ceil($totalItems / $perPage). Verify integer division isn't truncating: cast to float first if needed.
Symptom · 03
OTP or token generation produces predictable patterns (same code at the same time each day)
Fix
Check source: if you see rand() or mt_rand() for tokens, replace with random_int(). Also ensure the PHP version is 7.0+ (random_int() not available before).
Symptom · 04
fmod() returns unexpected large remainder or negative value
Fix
Inspect operand signs — fmod() result has the same sign as the dividend. If you need positive remainder, add the divisor and take fmod again: fmod(fmod($a, $b) + $b, $b). Use with absolute values if needed.
★ Quick Debug Cheat Sheet: PHP Math Function BugsThree common math-related problems and exactly what to run first.
Wrong decimal rounding in financial calculations
Immediate action
Locate the round() call and add the mode parameter as PHP_ROUND_HALF_UP.
Commands
grep -rn 'round(' app/ --include='*.php'
Check `echo ini_get('precision');` in a debug script.
Fix now
Set ini_set('precision', 14); in the bootstrap and use round($value, 2, PHP_ROUND_HALF_UP); everywhere.
Pagination shows fewer pages than expected+
Immediate action
Look for round() used on division result – replace with ceil().
Commands
grep -rn 'round.*pagin\|round.*\/.*per' app/ --include='*.php'
Manually calculate: what does ceil(21/10) vs round(21/10) return?
Fix now
Replace $pages = round($total / $per); with $pages = ceil($total / $per);.
Generated random codes are being guessed+
Immediate action
Check the function used: if it's rand() or mt_rand(), replace with random_int().
Commands
grep -rn 'rand(' app/ --include='*.php' | grep -v 'random_int'
Check PHP version: `php -v | grep ^PHP` – requires 7.0+ for random_int().
Fix now
Replace all occurrences with random_int($min, $max);. On PHP <7.0, use random_int() polyfill or openssl_random_pseudo_bytes().
FunctionUse CaseHandles Floats?Security Safe?PHP Version
abs()Remove negative sign from a numberYesN/AAll
round()Round to nearest value with optional precisionYesN/AAll
ceil()Always round UP to next integerYesN/AAll
floor()Always round DOWN to previous integerYesN/AAll
fmod()Remainder of float division (like % but for floats)YesN/AAll
pow()Raise number to a power (same as **)YesN/AAll
sqrt()Square root of a numberYesN/AAll
rand()Basic random integerNo (integers only)NoAll
mt_rand()Better random integer, fast, non-secureNo (integers only)NoAll
random_int()Cryptographically secure random integerNo (integers only)YesPHP 7.0+

Key takeaways

1
ceil() always goes up, floor() always goes down, round() goes to the nearest
and pagination always needs ceil(), never round().
2
fmod() is the float-safe replacement for the % operator
using % on decimals is a silent bug that PHP won't warn you about.
3
random_int() is the only safe choice for security-sensitive random values in PHP 7+
rand() and mt_rand() are predictable enough to be exploited.
4
pow($base, $exp) and $base ** $exp do exactly the same thing
** is the modern, more readable shorthand introduced in PHP 5.6.
5
Compose math functions carefully
each step changes the value's meaning. Always test edge cases (0, negative, NAN, INF) before shipping to production.
6
Use PHP_ROUND_HALF_UP explicitly in all financial contexts to avoid bank rounding discrepancies.

Common mistakes to avoid

5 patterns
×

Using round() for pagination

Symptom
The last page of results disappears when item count isn't a clean multiple of page size. For example, 21 items with 10 per page: round(21/10) = 2, but there are 3 pages needed.
Fix
Always use ceil($totalItems / $perPage) for page count. Verify with boundary tests: 1 item, N+1 items, 0 items (handle division by zero).
×

Using % (modulo) on float values

Symptom
Completely wrong remainder with no error or warning. For example, 10.5 % 3.2 silently computes as 10 % 3 = 1, when the correct remainder is 0.9.
Fix
Replace % with fmod() whenever either operand has a decimal point. Example: fmod(10.5, 3.2) returns 0.9. If you need a positive remainder, use: fmod(fmod($a, $b) + $b, $b).
×

Using rand() or mt_rand() for security tokens

Symptom
No immediate error, but generated tokens (OTPs, password reset links, discount codes) are statistically predictable. An attacker who collects a few outputs can predict future values.
Fix
Always use random_int() in PHP 7+ for any value that controls access or carries monetary value. For prior PHP versions, use openssl_random_pseudo_bytes() or a polyfill.
×

Forgetting rounding mode in financial calculations

Symptom
Discrepancies of $0.01 in invoices for certain products due to default 'round half away from zero' vs 'round half to even' (bankers rounding). Depending on PHP's ini precision and rounding mode, totals can be off.
Fix
Explicitly set rounding mode: round($value, 2, PHP_ROUND_HALF_UP). Also check that ini_get('precision') is set to 14 (default) or higher for consistency.
×

Using max() or min() on empty arrays

Symptom
max([]) returns false, which may be treated as 0 in arithmetic but can cause type-strict errors. For example, max([]) + 1 produces 1 (casting false to 0), but if you pass the result to a function expecting int, you'll get a TypeError.
Fix
Always guard: $max = $array ? max($array) : null; then check for null before using. Alternatively, use array_reduce with a default.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What is the difference between round(), ceil() and floor() in PHP — and ...
Q02JUNIOR
Why should you use random_int() instead of rand() when generating a pass...
Q03SENIOR
If you run fmod(10.5, 3.2) versus 10.5 % 3.2 in PHP, do you get the same...
Q04SENIOR
How do you safely calculate the number of pages needed to display search...
Q05SENIOR
Describe a scenario where you would combine abs(), min() and ceil() in o...
Q01 of 05JUNIOR

What is the difference between round(), ceil() and floor() in PHP — and can you give a real-world use case where choosing the wrong one would produce a bug?

ANSWER
round() rounds to the nearest number, with an optional precision and mode. It's for displaying prices. ceil() always rounds up — used for billing pages, shipping weight tiers. floor() always rounds down — used for completed units (e.g., episodes watched). A common bug is using round() for pagination: if you have 21 items and show 10 per page, ceil(21/10) = 3 pages, but round(21/10) = 2 pages, which hides the last page with 1 item.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
What is the difference between rand() and random_int() in PHP?
02
How do I round a number to 2 decimal places in PHP?
03
Why does PHP give the wrong answer when I use % on decimal numbers?
04
Can I use mt_rand() for generating discount codes?
05
What is the best way to get the absolute value of a number and then round it down?
🔥

That's PHP Basics. Mark it forged?

5 min read · try the examples if you haven't

Previous
PHP File Handling
11 / 14 · PHP Basics
Next
PHP Date and Time