Strings7 min readExamples run on PHP 8.5.10

strnatcmp()

Learn how PHP 8.4 strnatcmp() compares strings the way people read numbers, with examples, edge cases, return value rules and common mistakes.

Signature

strnatcmp(string $string1, string $string2): int

Purpose

strnatcmp() compares two strings using a "natural order" algorithm. Ordinary byte-by-byte comparison puts img10.png before img2.png, because the character 1 sorts before 2. A person reads the digit runs as whole numbers and expects img2.png to come first. strnatcmp() follows that human expectation: when it meets a run of digits in both strings, it compares the runs by numeric value instead of character by character.

It is most useful for file names, version-like labels, product codes and other identifiers that mix text and numbers.

Signature

Parameters

  • $string1: the first string to compare.
  • $string2: the second string to compare.

Neither parameter has a default value, so both are required. The comparison is case sensitive. If you need case-insensitive natural ordering, use strnatcasecmp() instead.

Return value

The function returns an int:

  • a value less than 0 if $string1 is less than $string2;
  • a value greater than 0 if $string1 is greater than $string2;
  • 0 if the two strings are considered equal.

Only the sign of the result has meaning. Since PHP 8.2 the function is no longer guaranteed to return the difference in string lengths when the lengths differ. It may simply return -1 or 1. Never rely on the magnitude, and never compare the result with a specific value such as -1 unless you first normalize it, for example with <=> 0.

Failure cases

The function does not return false and does not signal failure through its return value. Failures come from argument types:

  • With declare(strict_types=1), passing a non-string such as an integer throws a TypeError.
  • Without strict types, scalar values such as integers are converted to strings before comparison.
  • Passing null to these non-nullable parameters is deprecated in modern PHP, so convert or validate values first.

Example 1: natural versus byte order

The helper below reduces the result to -1, 0 or 1, which makes the output independent of the exact magnitude returned.

Example
<?php
function sign(int $n): int
{
    return $n <=> 0;
}

echo json_encode([
    sign(strnatcmp("img2.png", "img10.png")),
    sign(strcmp("img2.png", "img10.png")),
    sign(strnatcmp("img10.png", "img10.png")),
]), "\n";
Output
[-1,1,0]

The natural comparison says img2.png is smaller than img10.png. strcmp() disagrees, because it stops at the first differing character, 2 versus 1.

Example 2: sorting with a callback

Because strnatcmp() takes two strings and returns an integer, it works directly as a comparison callback for usort().

Example
<?php
$names = ["img12.png", "img10.png", "img2.png", "img1.png"];
$natural = $names;
$plain = $names;
usort($natural, 'strnatcmp');
usort($plain, 'strcmp');
echo json_encode($natural), "\n";
echo json_encode($plain), "\n";
Output
["img1.png","img2.png","img10.png","img12.png"]
["img1.png","img10.png","img12.png","img2.png"]

Copying the array first lets you compare both orderings from the same input. If you only need to sort an array in place, natsort() does a similar job while preserving keys.

Example 3: case sensitivity

Example
<?php
function sign(int $n): int
{
    return $n <=> 0;
}

echo json_encode([
    sign(strnatcmp("Alpha2", "alpha2")),
    sign(strnatcasecmp("Alpha2", "alpha2")),
    sign(strnatcmp("a10", "a9")),
]), "\n";
Output
[-1,0,1]

An uppercase A has a lower byte value than a lowercase a, so the case-sensitive call reports the first string as smaller. strnatcasecmp() ignores that difference and reports equality. The last value shows that a10 is greater than a9 once the digits are read as numbers.

Edge cases

  • Only digit runs are numeric. Text portions are still compared character by character, so file9 and folder1 are decided by the letters before the digits.
  • Leading zeros and spaces. The algorithm has special handling for them. If your data contains values like 01 or padded names, test those exact inputs rather than assuming a result.
  • Not a version comparer. Strings such as 1.10 and 1.9 are treated as text with digit runs. For real version numbers, use version_compare().
  • Locale is not involved. The comparison works on bytes, so it does not apply language-specific collation rules, and it is not suited to accented or multibyte text ordering.
  • Empty strings. An empty string is smaller than a non-empty one, and two empty strings are equal.

Common mistakes

  1. Testing for exactly -1 or 1. Only the sign is meaningful. Use < 0, > 0 or === 0.
  2. Forgetting case sensitivity. Img2 and img2 are not equal; switch to strnatcasecmp() when appropriate.
  3. Using == on strings to compare numbers. Natural comparison is for ordering strings, not for deciding numeric equality.
  4. Expecting stable locale-aware sorting. Use dedicated collation tools when human-language ordering matters.
  5. Passing mixed types under strict typing. Cast or validate values before calling.
  • strnatcasecmp(): natural order, ignoring case.
  • strcmp(): binary-safe comparison without natural number handling.
  • strcasecmp(): binary-safe, case-insensitive comparison.
  • natsort() and natcasesort(): sort whole arrays using natural order.
  • version_compare(): compare version strings.

Recap

strnatcmp() orders strings the way people read embedded numbers, so img2 comes before img10. It is case sensitive, returns an integer whose sign is the only reliable information, and fits naturally into usort(). Normalize the result with <=> 0 when you need predictable values, and choose a related function when you need case-insensitivity or true version comparison.

Tested examples

Natural order versus byte order
<?php
function sign(int $n): int
{
    return $n <=> 0;
}

echo json_encode([
    sign(strnatcmp("img2.png", "img10.png")),
    sign(strcmp("img2.png", "img10.png")),
    sign(strnatcmp("img10.png", "img10.png")),
]), "\n";
Output
[-1,1,0]
Sorting file names with usort()
<?php
$names = ["img12.png", "img10.png", "img2.png", "img1.png"];
$natural = $names;
$plain = $names;
usort($natural, 'strnatcmp');
usort($plain, 'strcmp');
echo json_encode($natural), "\n";
echo json_encode($plain), "\n";
Output
["img1.png","img2.png","img10.png","img12.png"]
["img1.png","img10.png","img12.png","img2.png"]
Case sensitivity compared with strnatcasecmp()
<?php
function sign(int $n): int
{
    return $n <=> 0;
}

echo json_encode([
    sign(strnatcmp("Alpha2", "alpha2")),
    sign(strnatcasecmp("Alpha2", "alpha2")),
    sign(strnatcmp("a10", "a9")),
]), "\n";
Output
[-1,0,1]