PHP Numbers
PHP has two numeric types: Type Holds Examples int Whole numbers, positive or negative 0 , 42 , -17 , 1_000_000 float Numbers with a fractional part or in exponential notation 3.14 , -0.5 , 1.5e3 This
PHP has two numeric types:
| Type | Holds | Examples |
|---|---|---|
int | Whole numbers, positive or negative | 0, 42, -17, 1_000_000 |
float | Numbers with a fractional part or in exponential notation | 3.14, -0.5, 1.5e3 |
This lesson focuses on integers: how to write them, their limits, how arithmetic behaves, how to convert and validate numeric input, and what to do when numbers become too large. Floats have their own lesson (PHP Floats) because they behave differently in important ways.
Why Integers Deserve Careful Attention
Integers appear everywhere: quantities, IDs, prices stored in the smallest currency unit, page numbers, ages, timestamps, and counters. Most numeric bugs come from three sources: input that is not actually an integer, division that produces a float unexpectedly, and numbers that exceed the integer range.
Writing Integer Literals
Example
<?php
$decimal = 255;
$hex = 0xFF; // hexadecimal (base 16)
$octal = 0o377; // octal (base 8), explicit prefix — PHP 8.1+
$legacyOctal = 0377; // octal, legacy form: leading zero
$binary = 0b11111111; // binary (base 2)
$million = 1_000_000; // underscores for readability — PHP 7.4+
var_dump($decimal, $hex, $octal, $legacyOctal, $binary, $million);
Output
int(255)
int(255)
int(255)
int(255)
int(255)
int(1000000)
How It Works
- All five notations produce the same integer. The notation only affects how the number is written in source code, not how it is stored.
- Hexadecimal (
0x) is common for colors, byte values, and bit flags. - Octal is mainly used for Unix file permissions, such as
chmod($file, 0o644). The0oprefix (PHP 8.1+) is clearer than the legacy leading-zero form. - Binary (
0b) is useful when working with individual bits. - Underscores are ignored by PHP and make large numbers readable:
1_000_000is easier to check than1000000.
Warning: A leading zero changes the meaning of a number literal.
$code = 0123;is octal, equal to83, not one hundred twenty-three. Never write decimal numbers with leading zeros in code. (This rule applies to literals in source code only; the string"0123"from a form converts to123.)
Integer Size and Limits
On 64-bit systems (all modern servers), integers are stored in 8 bytes.
<?php
echo PHP_INT_MAX . "\n";
echo PHP_INT_MIN . "\n";
echo PHP_INT_SIZE . " bytes\n";
Output
9223372036854775807
-9223372036854775808
8 bytes
That is about ±9.2 quintillion — enough for IDs, timestamps, and most money amounts stored in paise or cents.
Integer Overflow
When a calculation exceeds PHP_INT_MAX, PHP does not wrap around or throw an error. The result silently becomes a float:
<?php
$max = PHP_INT_MAX;
var_dump($max);
var_dump($max + 1);
$counter = PHP_INT_MAX;
$counter++;
var_dump($counter);
Output
int(9223372036854775807)
float(9.2233720368547758E+18)
float(9.2233720368547758E+18)
Why This Matters
Floats have only about 15–17 significant digits of precision. Once a value becomes a float, its last digits can no longer be represented exactly. For example, the result shown is 9223372036854775808 — but PHP_INT_MAX + 2 would produce the same float, because the gap between neighboring floats at this size is larger than 1. Calculations near the limit silently lose precision. For numbers this large, use BCMath or GMP (covered below).
Arithmetic With Integers
Example
<?php
var_dump(10 + 5);
var_dump(10 - 15);
var_dump(6 * 7);
var_dump(10 / 2);
var_dump(7 / 2);
var_dump(intdiv(7, 2));
var_dump(7 % 3);
var_dump(2 ** 10);
var_dump(2 ** -1);
Output
int(15)
int(-5)
int(42)
int(5)
float(3.5)
int(3)
int(1)
int(1024)
float(0.5)
Division Returns a Float When the Result Is Not Whole
The / operator returns an int only when both operands are integers and the division is exact (10 / 2 → int(5)). Otherwise it returns a float (7 / 2 → float(3.5)). Code that expects an integer — such as an array index or a page number — must handle this explicitly.
Integer Division: intdiv()
intdiv($a, $b) divides and discards the fractional part, always returning an int. It truncates toward zero:
<?php
var_dump(intdiv(7, 2));
var_dump(intdiv(-7, 2));
Output
int(3)
int(-3)
Modulo: %
% returns the remainder of integer division. The sign of the result follows the left operand:
<?php
var_dump(7 % 3);
var_dump(-7 % 3);
var_dump(7 % -3);
Output
int(1)
int(-1)
int(1)
Modulo is used for:
- Checking even/odd:
$n % 2 === 0 - Alternating row styles in tables:
$index % 2 - Wrapping values in a range:
($hour + 5) % 24 - Running something every N items:
if ($i % 100 === 0)
% converts its operands to integers. For the remainder of floats, use fmod().
Exponentiation: **
2 ** 10 raises 2 to the power 10. A negative exponent produces a float (2 ** -1 → 0.5), and results larger than PHP_INT_MAX become floats.
Division by Zero
<?php
try {
echo 10 / 0;
} catch (DivisionByZeroError $e) {
echo "Error: " . $e->getMessage() . "\n";
}
try {
echo 10 % 0;
} catch (DivisionByZeroError $e) {
echo "Error: " . $e->getMessage() . "\n";
}
Output
Error: Division by zero
Error: Modulo by zero
Since PHP 8.0, dividing by zero with /, %, or intdiv() throws a DivisionByZeroError. In PHP 7, / produced a warning and returned false or INF. Check divisors that come from data (for example, the number of items when calculating an average) before dividing. Exceptions are covered in PHP Exceptions.
Converting Values to Integers
Casting and intval()
<?php
var_dump((int) "42");
var_dump((int) " 42");
var_dump((int) "42abc");
var_dump((int) "abc");
var_dump((int) 9.99);
var_dump((int) -9.99);
var_dump((int) "1e3");
var_dump((int) true);
var_dump((int) null);
Output
int(42)
int(42)
int(42)
int(0)
int(9)
int(-9)
int(1000)
int(1)
int(0)
Conversion Rules
| Input | Result | Rule |
|---|---|---|
| Numeric string | The number | Leading whitespace is allowed |
Leading-numeric string ("42abc") | The leading number | The rest is ignored |
Non-numeric string ("abc") | 0 | No error from a cast |
| Float | Truncated toward zero | 9.99 → 9, -9.99 → -9 (no rounding) |
Numeric string in exponent form ("1e3") | 1000 | Since PHP 7.1 |
true / false | 1 / 0 | |
null | 0 |
Casting never fails: it always returns some integer. That makes it unsuitable for validation — "abc" silently becomes 0. Validate first, then convert.
To round instead of truncating, use (int) round($value).
Other Bases
<?php
var_dump(intval("42", 8)); // interpret as octal
var_dump(intval("0x1A", 16)); // hexadecimal with prefix
var_dump(hexdec("ff"));
var_dump(bindec("1010"));
var_dump(octdec("777"));
var_dump(dechex(255));
var_dump(decbin(10));
Output
int(34)
int(26)
int(255)
int(10)
int(511)
string(2) "ff"
string(4) "1010"
intval() accepts a base as its second argument. hexdec(), bindec(), and octdec() convert strings in those bases to numbers, and dechex(), decbin(), and decoct() convert numbers into strings in those bases.
Validating Integer Input
Form fields, URL parameters, and JSON may contain anything. Choose a validation method that matches the rules you need.
Comparing Validation Functions
<?php
$inputs = ["42", "-42", "4.2", "007", " 42", "42abc", ""];
foreach ($inputs as $input) {
printf(
"%-8s is_numeric: %-5s ctype_digit: %-5s FILTER_VALIDATE_INT: %s\n",
"'$input'",
var_export(is_numeric($input), true),
var_export(ctype_digit($input), true),
var_export(filter_var($input, FILTER_VALIDATE_INT), true)
);
}
Output
'42' is_numeric: true ctype_digit: true FILTER_VALIDATE_INT: 42
'-42' is_numeric: true ctype_digit: false FILTER_VALIDATE_INT: -42
'4.2' is_numeric: true ctype_digit: false FILTER_VALIDATE_INT: false
'007' is_numeric: true ctype_digit: true FILTER_VALIDATE_INT: false
' 42' is_numeric: true ctype_digit: false FILTER_VALIDATE_INT: 42
'42abc' is_numeric: false ctype_digit: false FILTER_VALIDATE_INT: false
'' is_numeric: false ctype_digit: false FILTER_VALIDATE_INT: false
What Each Function Checks
| Function | Accepts | Notes |
|---|---|---|
is_numeric() | Any int or float format, including "4.2" and "1e3" | Too broad for "whole number" checks |
ctype_digit() | Only the characters 0–9, at least one | Rejects negatives and whitespace; accepts leading zeros. Pass strings only (passing an int is deprecated since PHP 8.1) |
filter_var(..., FILTER_VALIDATE_INT) | Valid integers, optional sign; surrounding whitespace is trimmed | Rejects leading zeros ("007"), decimals, and out-of-range values; returns the converted int or false |
filter_var() is usually the best choice: it validates and converts in one step.
Validating a Range
<?php
$options = ['options' => ['min_range' => 1, 'max_range' => 100]];
var_dump(filter_var("25", FILTER_VALIDATE_INT, $options));
var_dump(filter_var("0", FILTER_VALIDATE_INT, $options));
var_dump(filter_var("150", FILTER_VALIDATE_INT, $options));
Output
int(25)
bool(false)
bool(false)
Setting min_range and max_range enforces business rules — for example, a quantity between 1 and 100 — in the same call. Because a valid result could be 0 in other situations, always check the result with === false.
Integers and JavaScript / JSON
PHP integers are 64-bit, but JavaScript numbers are 64-bit floats that can represent integers exactly only up to 9007199254740991 (Number.MAX_SAFE_INTEGER, which is 2^53 − 1).
<?php
echo json_encode(['id' => 9007199254740993]);
Output
{"id":9007199254740993}
PHP produces the correct JSON, but a browser reading it with JSON.parse() gets 9007199254740992 — a different ID. Large IDs (such as those from distributed ID generators like Snowflake) should be sent to JavaScript as strings:
<?php
echo json_encode(['id' => (string) 9007199254740993]);
Output
{"id":"9007199254740993"}
When decoding JSON with integers larger than PHP_INT_MAX, PHP converts them to floats. The JSON_BIGINT_AS_STRING flag keeps them as strings:
<?php
$json = '{"amount": 99999999999999999999}';
var_dump(json_decode($json, true)['amount']);
var_dump(json_decode($json, true, 512, JSON_BIGINT_AS_STRING)['amount']);
Output
float(1.0E+20)
string(20) "99999999999999999999"
Numbers Beyond the Integer Range: BCMath and GMP
For values larger than PHP_INT_MAX, or when every digit must be exact, PHP provides two extensions that work with numbers stored as strings or objects:
| Extension | Best For | Example Function |
|---|---|---|
| BCMath | Arbitrary-precision decimal arithmetic (money, high-precision calculations) | bcadd(), bcmul(), bcdiv() |
| GMP | Very large integers (cryptography, number theory) | gmp_add(), gmp_pow() |
Example
<?php
echo bcadd('9223372036854775807', '1') . "\n";
echo bcmul('123456789123456789', '987654321') . "\n";
echo gmp_strval(gmp_pow(2, 100)) . "\n";
Output
9223372036854775808
121932631234567900112635269
1267650600228229401496703205376
All digits are exact. These extensions must be installed (php-bcmath, php-gmp on Linux). BCMath's use for decimal money calculations is covered in PHP Floats.
Formatting Integers for Display
<?php
$population = 1234567;
echo number_format($population) . "\n";
echo number_format($population, 0, '', ' ') . "\n";
$formatter = new NumberFormatter('en_IN', NumberFormatter::DECIMAL);
echo $formatter->format($population) . "\n";
$currency = new NumberFormatter('en_IN', NumberFormatter::CURRENCY);
echo $currency->formatCurrency($population, 'INR') . "\n";
Output
1,234,567
1 234 567
12,34,567
₹12,34,567.00
number_format()groups digits in thousands with a chosen separator.NumberFormatter(from theintlextension) follows the rules of a locale. Foren_IN, digits are grouped in the Indian system (lakhs and crores), and currency formatting adds the rupee symbol and decimals automatically.
Formatting produces strings for display. Keep calculations on the original integers.
Practical Example: Pagination
Pagination combines several integer operations: division, rounding up, clamping a value into a range, and calculating an offset.
<?php
function paginate(int $totalItems, int $perPage, int $requestedPage): array
{
$totalPages = max(1, (int) ceil($totalItems / $perPage));
$page = max(1, min($requestedPage, $totalPages));
$offset = ($page - 1) * $perPage;
return ['page' => $page, 'totalPages' => $totalPages, 'offset' => $offset];
}
$requested = filter_var($_GET['page'] ?? '1', FILTER_VALIDATE_INT) ?: 1;
foreach ([[47, 10, 3], [47, 10, 99], [47, 10, -2], [0, 10, 1]] as [$total, $perPage, $page]) {
$result = paginate($total, $perPage, $page);
printf(
"items=%d, page %d requested -> page %d of %d, offset %d\n",
$total, $page, $result['page'], $result['totalPages'], $result['offset']
);
}
Output
items=47, page 3 requested -> page 3 of 5, offset 20
items=47, page 99 requested -> page 5 of 5, offset 40
items=47, page -2 requested -> page 1 of 5, offset 0
items=0, page 1 requested -> page 1 of 1, offset 0
Step-by-Step Explanation
$totalItems / $perPagegives4.7for 47 items;ceil()rounds up to5.0, because the last partial page still needs a page.(int)converts the float result to an integer.max(1, ...)guarantees at least one page, even when there are no items.max(1, min($requestedPage, $totalPages))clamps the requested page into the valid range, so99becomes the last page and-2becomes the first.- The offset — how many items to skip in a database query (
LIMIT 10 OFFSET 20) — is($page - 1) * $perPage. - The
$requestedline shows how the page number would be read from the URL:filter_var()returns an int orfalse, and?: 1falls back to page 1 for invalid input (including0).
Common Mistakes
Leading Zeros in Literals
Effect: $pin = 0123; stores 83 (octal).
Fix: Do not write leading zeros on decimal literals; store codes with leading zeros (PIN codes, phone numbers) as strings.
Expecting / to Return an Integer
Effect: 7 / 2 is 3.5; using the result as an array index triggers a deprecation or wrong behavior.
Fix: Use intdiv() for integer division, or (int) round(...) / ceil() / floor() deliberately.
Validating With Casts
Effect: (int) "abc" becomes 0 and passes as a valid number.
Fix: Validate with filter_var(..., FILTER_VALIDATE_INT) first.
Using is_numeric() to Check for Whole Numbers
Effect: "4.2" and "1e3" pass.
Fix: Use FILTER_VALIDATE_INT or ctype_digit().
Ignoring Overflow
Effect: Large values silently become imprecise floats.
Fix: Use BCMath or GMP for values that can exceed PHP_INT_MAX.
Sending Large IDs to JavaScript as Numbers
Effect: IDs above 2^53 change value in the browser.
Fix: Encode them as strings in JSON.
Best Practices
- Use underscores in long numeric literals for readability, and
0ofor octal. - Store numbers with meaningful leading zeros (PIN codes, phone numbers, ZIP codes) as strings — they are identifiers, not quantities.
- Validate integer input with
filter_var()and range options. - Use
intdiv()when you need integer division, and check divisors before dividing. - Store money as integers in the smallest unit (paise, cents) or use BCMath.
- Use
number_format()orNumberFormatteronly for display.
Official reference: https://www.php.net/manual/en/language.types.integer.php