PHP Math Functions — mt_rand() Caused Revenue Loss
A sudden spike in high-value discounts on new accounts traced to mt_rand().
20+ years shipping production PHP systems at scale. Drawn from code that ran under real load.
- ✓Basic programming fundamentals
- ✓A computer with internet access
- ✓Willingness to follow along with examples
- 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
PHP's math functions are the raw arithmetic tools that power everything from rounding prices to generating random values, but they come with sharp edges that can cost you real money. The core functions — , abs(), max(), min(), fmod(), round(), ceil(), floor(), pow(), sqrt(), log(), rand(), and mt_rand() — are deceptively simple. random_int() uses the Mersenne Twister algorithm, which is fast but predictable: given enough outputs, an attacker can reconstruct its internal state.mt_rand()
This isn't theoretical — in 2021, a gambling site lost $200k because was used for shuffle-based payouts, and the sequence was reverse-engineered from observed results. The fix is mt_rand(), which uses cryptographically secure entropy from the OS, but it's slower and can throw exceptions if the system runs out of entropy (rare but possible in high-concurrency Docker containers).random_int()
Where these functions fit in the ecosystem matters. For rounding, uses banker's rounding (round half to even) by default, which can silently skew totals in financial calculations — you often need round()round($price, 2, PHP_ROUND_HALF_UP) to get the expected behavior. is your friend for modulo with floats, but it inherits floating-point precision issues: fmod()fmod(2.0, 0.1) returns 0.0999999999999999, not 0.
For business logic like tax brackets or discount tiers, and min() are essential for clamping values, but they don't handle nulls gracefully — max()min(null, 5) returns null, not 5. When NOT to use these: never use or mt_rand() for anything involving money, security tokens, or session IDs.rand()
Use for that. For high-performance Monte Carlo simulations where predictability isn't a concern, random_int() is still fine — just don't let it touch revenue.mt_rand()
Real-world patterns combine these functions into business logic. A common pattern for price display: round(max($minPrice, min($maxPrice, $rawPrice)), 2) ensures values stay within bounds. For progressive discount tiers: $discount = min(0.3, floor($orderTotal / 100) * 0.05) caps at 30%.
The logarithmic functions shine in scaling — pow(2, floor(log($userCount, 2))) gives you the nearest lower power of two for sharding. But the killer pattern is random selection without bias: $items[random_int(0, count($items) - 1)] is the only safe way to pick a winner in a lottery.
The lesson from the revenue loss isn't that PHP math is broken — it's that you must understand the mathematical properties of each function before using it in a financial context. A 0.0001% bias in random number generation, when applied to millions of transactions, becomes a predictable exploit.mt_rand()
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.
Why mt_rand() Lost Revenue — The Real Math Behind PHP's Random Functions
PHP's math functions are a collection of built-in operations for arithmetic, number theory, and randomization — but the critical distinction isn't between 'math' and 'not math.' It's between deterministic precision and statistical randomness. Functions like mt_rand(), rand(), and random_int() all produce numbers, but only one is cryptographically safe. The others are predictable given enough samples, which is why mt_rand() caused real revenue loss in online poker and lottery systems: attackers reverse-engineered the seed from observed outputs.
In practice, mt_rand() uses the Mersenne Twister algorithm — fast, uniform, but not secure. Its internal state (624 32-bit integers) can be fully reconstructed after observing 624 consecutive outputs. Once the state is known, every future 'random' number is deterministic. This is O(1) to compute for an attacker. Meanwhile, random_int() uses system entropy (/dev/urandom on Linux) and is suitable for security-sensitive contexts like token generation or shuffle seeding.
Use mt_rand() only when performance matters and security does not — e.g., A/B test assignment, load balancing, or non-critical shuffles. For anything involving money, authentication, or secrets, use random_int() or random_bytes(). The cost of a predictable seed in production is not a bug; it's a liability.
mt_rand() for session tokens, password resets, or cryptographic keys.mt_rand() to shuffle decks — attackers scraped 624 hands and predicted all future cards.random_int() for any value that affects control flow or secrets.mt_rand() vs random_int() is irrelevant when the cost of failure is revenue or trust.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.
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()abs(-50) gives you 50, which is the amount, regardless of direction. You use this whenever you care about distance or magnitude, not direction.
and max() 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.min()
is the floating-point version of the fmod()% (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. handles decimal remainders properly — use it whenever your numbers aren't whole.fmod()
<?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! ?>
fmod() when either number has a decimal point.min() are cheap: O(n) for arrays, O(1) for two arguments.max() on an empty array returns false, not 0 — always check array emptiness first.max()/min() clamp values, fmod() handles floats.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. asks 'which step are you closer to?' — you're closer to 8, so it picks 8. round() (ceiling) always goes UP — you're above step 7, so it goes to 8. ceil() always goes DOWN — you're below step 8, so it stays at 7.floor()
is your default for prices and displaying data. It takes an optional second argument for decimal precision — round()round(9.999, 2) gives you 10.00. This is what you use for a final invoice total.
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()ceil(2.1) gives 3.
is for things that only count when complete. A user watched 4.8 episodes — they completed 4. floor()floor(4.8) gives 4. Also commonly used in pagination calculations.
<?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 ?>
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.round() for pagination is the #1 math bug in e-commerce production — it silently truncates the last page.ceil($weight / $perKg) * $rate.ceil() for conservative estimates, floor() for completed units.ceil() — never round().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()
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.sqrt()
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()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.
<?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) ?>
pow() — 2 8 equals 256. Both are valid, but is more readable for simple cases. Interviewers love asking which is more 'modern' — it's .pow() for base/exponent variables.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 . It's fine for non-security uses like shuffling a quiz order, picking a random featured article, or generating test data.rand()
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 . If it's just for fun or display, random_int() is fine.mt_rand()
<?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 ?>
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.random_bytes() for binary tokens.random_bytes() with bin2hex() — never rely on mt_rand() alone.mt_rand() is okay for non-security, random_int() is the only safe choice for secure values.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: alone is not enough for accurate tax calculations. You need to first round intermediate results, then apply rounding mode. Use round() with round()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 and pow() to compute Haversine distance between two latitude/longitude points. Use sqrt() and rad2deg() for conversions.deg2rad()
Pagination with boundary checks: Use for page count, ceil()max(1, ...) to ensure minimum page 1, and to cap at the last page. Then use min() for display count on the final page. Always test edge cases: 0 items, 1 item, exactly N items.round()
<?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 ?>
- 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
max(array_map('abs', $negatives)) works, but abs(max($negatives)) gives the negative value closest to zero, not the furthest.number_format() with round() to avoid display rounding errors like 0.1 + 0.2 = 0.30000000000000004.Integer Division: The Silent Bug in Your Payment Calculations
Most devs treat integer division like it's just regular division with extra steps. That naivety costs real money. PHP's intdiv() function does exactly what it says: divides two integers and returns the integer quotient, tossing the remainder like a bad penny. This isn't for rounding. This is for when you need exact counts of things that cannot be fractional: seats on a flight, items in a batch, or the number of full shipping containers needed. The key insight: intdiv() throws a DivisionByZeroError if you pass zero as the divisor. No silent NaN, no false infinity. Your code blows up immediately, which is a feature, not a bug. contrast this with the sloppy floor($x / $y) pattern that silently produces garbage when dealing with negative numbers. Negative remainders crash inventory systems. Use intdiv(). Your on-call rotation will thank you.
// io.thecodeforge — php tutorial $itemsInWarehouse = 50007; $batchSize = 50; // Correct: integer division for discrete units $fullBatches = intdiv($itemsInWarehouse, $batchSize); echo "Full batches: $fullBatches\n"; // Wrong: float floor can hide negative remainders $negativeStock = -13; $batchesNegative = intdiv($negativeStock, 4); echo "Negative stock batches: $batchesNegative\n"; // Division by zero kills execution immediately // $crash = intdiv(100, 0); // Uncaught DivisionByZeroError: Division by zero
intdiv() when you need fractional precision for monetary calculations. It drops the remainder ruthlessly. That's 0.07 cents you just lost per transaction.exp() and log10(): Exponential Growth Is Not Optional Reading
If you think PHP's exp() and log10() are only for scientists and statisticians, you are missing half your toolkit. exp() raises Euler's number (e ≈ 2.718) to the power of your argument. That's compound interest. That's radioactive decay. That's the half-life of a user's engagement after a redesign. log10() returns the base-10 logarithm. It tells you orders of magnitude. Is your dataset growing? The difference between 1,000 and 10,000 rows is exactly one order of magnitude on a log10 scale. Use these when you need to detect exponential trends, scale features like time decay in recommendation engines, or normalize data across vastly different ranges. The WHY: most business logic assumes linear relationships. Real systems are exponential. You want to spot the hockey stick curve before it hits you in the face. Respect the exponent.
// io.thecodeforge — php tutorial $daysSincePublish = 30; $decayConstant = -0.05; // daily decay factor // exp() models continuous exponential decay $retentionMultiplier = exp($decayConstant * $daysSincePublish); echo "Retention after $daysSincePublish days: " . round($retentionMultiplier * 100, 2) . "%\n"; $pageViews = [150, 3200, 87000, 245000]; foreach ($pageViews as $views) { $orderOfMagnitude = $views > 0 ? log10($views) : 0; echo "$views views -> order of magnitude: " . round($orderOfMagnitude, 2) . "\n"; }
log10() reveals orders of magnitude. Your linear-thinking teammates will be baffled.Trigonometric Functions: The Most Underused Tools in Web Development
Trigonometry in PHP? Before you roll your eyes, consider this: map-based applications, game leaderboards, animated charts, and even some CAPTCHA systems all rely on sin(), cos(), and atan2(). These functions work in radians, not degrees. That catches everyone at least once. The WHY: radians are natural units for circles. One complete revolution is 2π radians. Once you internalise that, coordinate systems become trivial. atan2($y, $x) is your best friend for finding the angle between two points without dealing with divide-by-zero edge cases. Want to draw a circle on your canvas? sin() and cos() with a loop. Want to rotate a user's avatar smoothly? Same functions. Want to compute a geofence boundary? atan2 gives you bearing between lat/lng points. Stop treating these as academic. They are practical, production-ready, and your competition probably ignores them. That's your edge.
// io.thecodeforge — php tutorial function bearingFromCoordinates(float $lat1, float $lon1, float $lat2, float $lon2): float { $degs = deg2rad(1); $lat1Rad = $lat1 * $degs; $lon1Rad = $lon1 * $degs; $lat2Rad = $lat2 * $degs; $lon2Rad = $lon2 * $degs; $deltaLon = $lon2Rad - $lon1Rad; $x = sin($deltaLon) * cos($lat2Rad); $y = cos($lat1Rad) * sin($lat2Rad) - sin($lat1Rad) * cos($lat2Rad) * cos($deltaLon); $bearing = atan2($x, $y); // radians return fmod(rad2deg($bearing) + 360, 360); // degrees, 0-360 } echo "Bearing from SF to LA: " . round(bearingFromCoordinates(37.7749, -122.4194, 34.0522, -118.2437), 2) . "°\n"; echo "Bearing from LA to SF: " . round(bearingFromCoordinates(34.0522, -118.2437, 37.7749, -122.4194), 2) . "°\n";
Lost Revenue from Random Discount Codes
- Never use
mt_rand()orrand()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.
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.round() in pagination code. Replace with ceil($totalItems / $perPage). Verify integer division isn't truncating: cast to float first if needed.rand() or mt_rand() for tokens, replace with random_int(). Also ensure the PHP version is 7.0+ (random_int() not available before).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.grep -rn 'round(' app/ --include='*.php'Check `echo ini_get('precision');` in a debug script.ini_set('precision', 14); in the bootstrap and use round($value, 2, PHP_ROUND_HALF_UP); everywhere.grep -rn 'round.*pagin\|round.*\/.*per' app/ --include='*.php'Manually calculate: what does ceil(21/10) vs round(21/10) return?$pages = round($total / $per); with $pages = ceil($total / $per);.grep -rn 'rand(' app/ --include='*.php' | grep -v 'random_int'Check PHP version: `php -v | grep ^PHP` – requires 7.0+ for random_int().random_int($min, $max);. On PHP <7.0, use random_int() polyfill or openssl_random_pseudo_bytes().| Function | Use Case | Handles Floats? | Security Safe? | PHP Version |
|---|---|---|---|---|
| abs() | Remove negative sign from a number | Yes | N/A | All |
| round() | Round to nearest value with optional precision | Yes | N/A | All |
| ceil() | Always round UP to next integer | Yes | N/A | All |
| floor() | Always round DOWN to previous integer | Yes | N/A | All |
| fmod() | Remainder of float division (like % but for floats) | Yes | N/A | All |
| pow() | Raise number to a power (same as **) | Yes | N/A | All |
| sqrt() | Square root of a number | Yes | N/A | All |
| rand() | Basic random integer | No (integers only) | No | All |
| mt_rand() | Better random integer, fast, non-secure | No (integers only) | No | All |
| random_int() | Cryptographically secure random integer | No (integers only) | Yes | PHP 7.0+ |
| File | Command / Code | Purpose |
|---|---|---|
| building_blocks.php | $temperatureDrop = -12.5; // temperature fell by 12.5 degrees | The Building Blocks |
| rounding_functions.php | $rawPrice = 19.9867452; // price after tax calculation | Rounding Numbers |
| powers_and_roots.php | $principal = 1000.00; // initial investment in dollars | Power, Square Root and Logarithms |
| random_numbers.php | $oldRandomNumber = rand(1, 100); | Random Numbers Done Right |
| real_world_patterns.php | $discountPercent = -20; // user input, could be negative | Real-World Patterns |
| InventoryBatchAllocation.php | $itemsInWarehouse = 50007; | Integer Division |
| EngagementDecayRate.php | $daysSincePublish = 30; | exp() and log10() |
| GeoBearingCalculator.php | function bearingFromCoordinates(float $lat1, float $lon1, float $lat2, float $lo... | Trigonometric Functions |
Key takeaways
floor() always goes down, round() goes to the nearestceil(), never round().mt_rand() are predictable enough to be exploited.Common mistakes to avoid
5 patternsUsing round() for pagination
Using % (modulo) on float values
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
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
Using max() or min() on empty arrays
Interview Questions on This Topic
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?
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.Why should you use random_int() instead of rand() when generating a password reset token in PHP?
rand() uses a linear congruential generator with a small state (32 bits) that is predictable if an attacker observes enough outputs. An attacker who obtains a few consecutive tokens can reconstruct the seed and predict all future tokens. In PHP 7.0+, random_int() is available and should be used for anything security-related.If you run fmod(10.5, 3.2) versus 10.5 % 3.2 in PHP, do you get the same result? Why or why not?
fmod() when dealing with floats.How do you safely calculate the number of pages needed to display search results, considering edge cases like zero results?
floor() to determine which items to show: $offset = ($currentPage - 1) * $perPage; then slice the array.Describe a scenario where you would combine abs(), min() and ceil() in one piece of logic. What problem does it solve?
Frequently Asked Questions
rand() uses a weak pseudo-random algorithm that is fast but statistically predictable. random_int() uses cryptographically secure randomness sourced from the operating system and is safe for security-sensitive values like OTPs and tokens. For anything beyond visual randomness (shuffling a quiz, picking a display colour), always choose random_int().
Use round($number, 2) — the second argument is the number of decimal places you want. For example, round(19.9867, 2) returns 19.99. This is the correct approach for displaying prices and financial values.
The % operator in PHP is designed for integers only. When you use it on floats, PHP silently truncates both numbers to integers before calculating the remainder — giving you a wrong result with no warning. Replace % with fmod() whenever either value has a decimal point, for example fmod(10.5, 3.2) instead of 10.5 % 3.2.
No. mt_rand() is predictable; an attacker can reconstruct the seed after observing a few outputs. Policy in production should be: use random_int() for anything that controls access or has monetary value. mt_rand() is acceptable only for non-security purposes like selecting a random background colour or shuffling a quiz order.
You can chain functions: floor(abs($number)). For example, floor(abs(-4.7)) returns 4. If you need to round down after getting the absolute, that's correct. But be careful: floor(abs(-4.7)) = 4, while floor(-4.7) = -5. The order matters — abs() first then floor() gives a non-negative integer.
20+ years shipping production PHP systems at scale. Drawn from code that ran under real load.
That's PHP Basics. Mark it forged?
6 min read · try the examples if you haven't