PHPIntegers & Floats

Integers & Floats

PHP gives you two numeric scalar types for arithmetic: int for whole numbers and float (also called double) for numbers with a fractional part. Unlike languages such as C or Java, you never declare the width of an integer or choose between float and double — PHP picks a sensible platform-dependent size for you and silently converts between the two types as arithmetic demands it. That convenience is also where the surprises live: integers can overflow into floats without warning, and floats can never represent most decimal fractions exactly. Understanding both behaviors up front saves you from very confusing bugs later, especially around money and comparisons.

Integers: whole numbers with a platform-dependent ceiling

An int in PHP is a whole number, positive or negative, with no decimal point. Internally PHP typically uses the native word size of the machine it runs on — 64 bits on virtually every server and desktop today — which gives you a huge but still finite range. You can inspect the exact limits with the built-in constants PHP_INT_MAX, PHP_INT_MIN, and PHP_INT_SIZE.

Inspecting the integer range

PHP
<?php

echo PHP_INT_MAX . PHP_EOL;   // largest representable int
echo PHP_INT_MIN . PHP_EOL;   // smallest representable int
echo PHP_INT_SIZE . PHP_EOL;  // size in bytes (8 on a 64-bit build)
9223372036854775807
-9223372036854775808
8

Because that ceiling is tied to the platform, code that assumes a specific integer width (say, 32-bit wraparound behavior from another language) will behave differently on a 64-bit PHP build. If you ship code that must run identically everywhere, don't hardcode assumptions about PHP_INT_MAX — read the constant instead.

Integer overflow: PHP promotes to float instead of wrapping

Many languages let integers "wrap around" on overflow (a max value plus one silently becomes the minimum value). PHP does not do that. When an integer operation would exceed PHP_INT_MAX or go below PHP_INT_MIN, PHP automatically promotes the result to a float instead of throwing an error or wrapping. This keeps the number mathematically closer to correct, but it also means the result loses integer precision once it's large enough, because floats can't represent every large integer exactly either.

Overflow promotes silently to float

PHP
<?php

$max = PHP_INT_MAX;
var_dump($max);        // int(9223372036854775807)
var_dump($max + 1);    // float(9.2233720368548E+18)

echo gettype($max + 1) . PHP_EOL;
int(9223372036854775807)
float(9.2233720368548E+18)
double
Overflow is silent — no error, no exception
There is no warning when this happens. If you're accumulating a counter, a hash, or an ID in a loop and it can plausibly exceed `PHP_INT_MAX`, you will silently get a `float` result with reduced precision rather than a crash. Validate ranges explicitly if exact integer results matter (for example, using `bcmath` or `gmp` for arbitrary-precision arithmetic).
Floats: fast, but not exact

A float stores numbers using the IEEE 754 double-precision format, the same representation used by nearly every mainstream language. That format is binary, which means it can represent binary fractions exactly (like 0.5 or 0.25) but cannot exactly represent most decimal fractions (like 0.1 or 0.2), for the same reason 1/3 has no exact finite decimal representation. The classic symptom:

The 0.1 + 0.2 surprise

PHP
<?php

$sum = 0.1 + 0.2;

var_dump($sum === 0.3);           // bool(false)
printf("%.20f\n", $sum);          // shows the tiny binary rounding error
echo $sum . PHP_EOL;               // 0.3 (default precision hides it)
bool(false)
0.30000000000000004441
0.3

PHP's default echo/print formatting rounds the displayed digits, which is why $sum looks like 0.3 even though the stored value is very slightly larger. The mismatch only becomes visible when you compare floats with === or ==, or when you print with enough decimal places using printf or number_format.

Never compare floats with == or ===
Because of binary rounding, two floats that are "the same number" mathematically can differ in their last bits. Instead of exact equality, compare against a small tolerance (an epsilon):

Safe float comparison

PHP
<?php

function floatsAreEqual(float $a, float $b, float $epsilon = 1e-9): bool
{
    return abs($a - $b) < $epsilon;
}

var_dump(floatsAreEqual(0.1 + 0.2, 0.3)); // bool(true)
bool(true)
Division: / always risks a float, intdiv() never does

PHP's / operator always performs floating-point-capable division — if the two operands divide evenly, you get an int-looking value that's actually still typed as whatever the division produced (an int when it divides evenly and both operands are int, a float otherwise). When you specifically want integer division — the quotient truncated toward zero, discarding the remainder — use intdiv() rather than casting the result of /.

/ vs intdiv()

PHP
<?php

var_dump(10 / 4);        // float(2.5)
var_dump(10 / 5);        // int(2)  — divides evenly
var_dump(intdiv(10, 4)); // int(2)  — truncates toward zero
var_dump(intdiv(-10, 4)); // int(-2) — truncates toward zero, not floor
float(2.5)
int(2)
int(2)
int(-2)
intdiv() throws, / warns
Dividing by zero with `intdiv()` throws a `DivisionByZeroError`, while `10 / 0` raises a `DivisionByZeroError` as well since PHP 8 (earlier versions only emitted a warning and returned `INF`). Wrap divisions by user-supplied denominators in a check or a try/catch.
Type juggling between int and float

Expression

Result type

Why

5 + 2

int

Both operands are int and the operator does not force a float

5 / 2

float

Does not divide evenly

5.0 + 2

float

Any float operand promotes the whole expression

(int) 5.9

int (5)

Casting to int truncates, it does not round

PHP_INT_MAX + 1

float

Overflow promotion

Casting truncates, it doesn't round

PHP
<?php

var_dump((int) 5.9);   // int(5)  — not 6
var_dump((int) -5.9);  // int(-5) — truncates toward zero
var_dump((int) "42abc"); // int(42) — leading numeric prefix is parsed
int(5)
int(-5)
int(42)
  • int size is platform-dependent; read PHP_INT_MAX/PHP_INT_SIZE instead of hardcoding 32- or 64-bit assumptions.

  • Integer overflow promotes to float silently — no warning, no exception.

  • Floats use binary IEEE 754 representation, so most decimal fractions are only approximated — never compare them with ==/===.

  • / can return either int or float depending on whether it divides evenly; intdiv() always returns int and truncates toward zero.

  • Casting a float to int truncates the fractional part; it does not round.

Tip
For money or anything requiring exact decimal arithmetic (invoicing, accounting, financial totals), don't use `float` at all. Either store amounts as integer cents, or use the `bcmath` extension (`bcadd`, `bcmul`, etc.), which does arbitrary-precision arithmetic on numeric strings and sidesteps binary rounding entirely.