In PHP, what happens when an integer calculation exceeds PHP_INT_MAX, and how do you work with integers that large?
answer
- no exception, no wraparound
- result silently becomes float
- PHP_INT_MAX: 2^63 - 1 on 64-bit
- floats exact only up to 2^53
- gmp or bcmath for bigger
basics
~20 sPHP does not throw or wrap on integer overflow: the result silently becomes a float. On 64-bit builds PHP_INT_MAX is 9223372036854775807, and floats above 2^53 cannot represent every integer, so precision is lost; use GMP or bcmath strings for larger values.
solid answer
~40 sPHP's `int` is a signed 64-bit integer on 64-bit builds (`PHP_INT_MAX` is `9223372036854775807`, `PHP_INT_SIZE` is `8`). When `+`, `-` or `*` produces a result outside that range, PHP neither throws nor wraps: it returns a **float**, and an integer literal too large for `int` is parsed as a float too. The danger is silent precision loss: a double holds every integer only up to 2^53, so `PHP_INT_MAX + 1 === PHP_INT_MAX + 2` is `true`. `is_int()` checks start failing, and converting such a float back to `int` wraps around and, since PHP 8.5, emits a warning. `intdiv(PHP_INT_MIN, -1)` is the case that throws, an `ArithmeticError`. For large values use the **GMP** extension (`gmp_init()`, `gmp_add()`, overloaded operators on `GMP` objects) or **bcmath** with numeric strings, and keep big identifiers as strings end to end.
code
php · 14 lines<?php
$a = PHP_INT_MAX + 1;
$b = PHP_INT_MAX + 2;
var_dump($a); // float(9.223372036854776E+18)
var_dump($a === $b); // bool(true): both are 2^63 as floats
try {
intdiv(PHP_INT_MIN, -1);
} catch (ArithmeticError $e) {
echo $e->getMessage(), PHP_EOL; // Division of PHP_INT_MIN by -1 is not an integer
}
echo gmp_strval(gmp_init(PHP_INT_MAX) + 2), PHP_EOL; // 9223372036854775809go deeper
Recall that PHP_INT_MAX is about 9.2e18 on 64-bit and that going past it turns the result into a float instead of throwing.
Explain why that float loses precision above 2^53, why intdiv(PHP_INT_MIN, -1) throws, and what PHP 8.5 changed about converting such floats back to int.
Keep large identifiers as strings, guard native arithmetic before it overflows, and choose GMP or bcmath where exact large values are part of the domain.
Define where native integers are safe and where arbitrary precision is mandatory, and make that visible in types and data contracts across services.
## The integer range A PHP `int` is a signed integer of the platform's word size. On the 64-bit builds used almost everywhere today: - `PHP_INT_SIZE` is `8` (bytes); - `PHP_INT_MAX` is `9223372036854775807` (2^63 - 1); - `PHP_INT_MIN` is `-9223372036854775808`. On a 32-bit build `PHP_INT_MAX` is `2147483647`, which is why older code and some Windows builds hit overflow much sooner. ## What overflow does Many languages either wrap around (`MAX + 1` becomes `MIN`) or throw. PHP does neither. When an integer operation's exact result does not fit, PHP computes it as a **float** instead: ```php <?php var_dump(PHP_INT_MAX); // int(9223372036854775807) var_dump(PHP_INT_MAX + 1); // float(9.223372036854776E+18) var_dump(9223372036854775808); // float(9.223372036854776E+18): literal too big for int var_dump(is_int(PHP_INT_MAX * 2)); // bool(false) ``` No warning, no notice. The value's type simply changes. ## Why the float is a problem A double has a 53-bit significand, so it can represent **every** integer only up to 2^53 (`9007199254740992`). Above that, consecutive integers collapse onto the same float. Near `PHP_INT_MAX` the gap between adjacent floats is 2048: | Expression | Result | |---|---| | `PHP_INT_MAX + 1` | `float`, exactly 2^63 | | `PHP_INT_MAX + 2` | `float`, also 2^63 | | `PHP_INT_MAX + 1 === PHP_INT_MAX + 2` | `true` | | `(int) (PHP_INT_MAX + 1)` | wraps to `PHP_INT_MIN`; PHP 8.5 adds a warning | The last row matters in PHP 8.5: converting a float that cannot be represented as an `int`, whether by an explicit `(int)` cast or implicitly, now emits `Warning: The float ... is not representable as an int, cast occurred`. Earlier versions produced the wrapped value silently. The one arithmetic operation that throws instead of producing a float is `intdiv(PHP_INT_MIN, -1)`: the true result, 2^63, is not an integer PHP can return, and `intdiv()` always returns `int`, so it throws `ArithmeticError`. ## Where this shows up in real code 1. **Large identifiers.** 64-bit IDs generated by distributed systems, or unsigned 64-bit database columns, can exceed `PHP_INT_MAX` or pass through a float somewhere in decoding. Keep them as **strings** end to end and compare them as strings. 2. **Multiplying counters.** Byte counts, microsecond timestamps multiplied by factors, or factorials overflow quietly and keep "working" with wrong low digits. 3. **Checks that assume `int`.** A value that became a float fails `is_int()` and strict `int` parameter declarations, often far from the arithmetic that caused it. ## Arbitrary-precision options PHP ships two extensions for numbers beyond native range; both must be enabled in the build: | Extension | Values | Interface | Best for | |---|---|---|---| | **GMP** | integers of any size | `GMP` objects with overloaded operators, plus `gmp_add()`, `gmp_mul()`, `gmp_strval()` | big integers, cryptography-style arithmetic, bit operations | | **bcmath** | decimals of any size and scale | numeric strings (`bcadd()`, `bcmul()`) and, since PHP 8.4, `BcMath\Number` objects | exact decimals such as money, fixed-scale results | ```php <?php $id = gmp_init('9223372036854775807'); $next = $id + 1; // GMP object, exact echo gmp_strval($next), PHP_EOL; // 9223372036854775808 ``` ## Guarding the native path When staying with native `int` is required, check before you compute rather than after: compare against `intdiv(PHP_INT_MAX, $factor)` before multiplying, or assert `is_int()` on results that must stay integral. Detecting overflow afterwards is unreliable because the float result may already have lost the digits you would need to notice it. ## Common mistakes - **Expecting an exception.** No `OverflowException` or `ArithmeticError` is raised for `+`, `-` or `*`; the type silently becomes `float`. - **Expecting wraparound.** `PHP_INT_MAX + 1` is not `PHP_INT_MIN`; only an explicit conversion of the resulting float back to `int` wraps. - **Trusting the float's digits.** Printing the overflowed value with `var_dump()` shows a plausible number whose low digits are already gone. - **Assuming 64 bits everywhere.** Code that must run on a 32-bit build should use `PHP_INT_MAX` and `PHP_INT_SIZE` instead of hard-coded limits.
- In PHP, why does PHP_INT_MAX + 1 === PHP_INT_MAX + 2 evaluate to true?Both sums overflow, so PHP computes them as floats. Near 2^63 adjacent doubles are 2048 apart, so the exact results 2^63 and 2^63 + 1 both round to the same float, 2^63. The comparison is between two identical floats, so `===` is true.
- In PHP, when would you choose GMP over bcmath for large numbers?GMP for integers: it is backed by the GMP library, supports overloaded operators on `GMP` objects and integer-specific operations such as modular exponentiation and bit functions. bcmath for exact decimals with a fixed scale, such as money, through numeric-string functions or `BcMath\Number`. Both require the extension to be enabled in the PHP build.
saying these in an interview costs you the question
- Says PHP throws an OverflowException when an int exceeds PHP_INT_MAX.
- Believes PHP_INT_MAX + 1 wraps around to PHP_INT_MIN.
- Thinks a float can hold any 64-bit integer exactly.
- Stores 64-bit identifiers as floats after decoding.
- Assumes PHP_INT_MAX is the same on every build.