Пишу, по большей части, про историю, свою жизнь и немного про программирование.

is_numeric

Функция is_numeric одна из традиционно неверно понимаемых программистами на ПХП. Её, обычно, понимаю как «посмотреть содержится ли в переменной что-то похожее на число». Например, таким образом проверяют пришло ли число в строке для последующего его преобразования в численный тип.

Беда в том, что понятия числа у ПХП и человека различаются. Посмотрите, все эти вызовы рассматриваемой функции вернут «true»:

var_dump(is_numeric("123e123")); // true — десятичное число в экспоненциальной записи
var_dump(is_numeric("0123")); // true — восьмеричное число
var_dump(is_numeric("0XDeadBeef")); // true — шестнадцатеричное число
var_dump(is_numeric(NAN)); // true — специальное численное значение «не число»

Особенно не везёт восьмеричным числам, которые во многих языках (и в ПХП) тоже начинаются с нуля: формально выглядят как обычное десятичное (только начинается с нуля), проходят все проверки, а после преобразования теряют ведущий ноль и это может быть важно.

С незапамятных времён существует хак, который позволяет убедиться, что перед нами именно десятичное число, пусть и в строковой форме:

function is_number($var) { return (string) (int) $var === (string) $var; }

Принцип очень простой — переменная преобразуется в целый тип (можно преобразовывать и в тип с плавающей точкой, если это нужно), потом берётся строковое представление получившегося числа. На этом этапе восьмеричные, шестнадцатеричные и прочие виды записи чисел будут преобразованы в целочисленный тип, строковое представление которого — десятичное число. Справа же пришедшее остаётся в первозданном виде.

Далее мы сравниваем левую и правую часть и, если они совпадают, значит к нам пришло именно десятичное число, так как никаких преобразований с ним не произошло.

Добавлено позднее: в документации к функции написано, что она так же понимает двоичную форму записи введёную недавно (число должно начинаться с «0b»), к сожалению, это неправда — в коде поддержки нет, баг заведён и в нём написано, что это проблема документации, а не функции.

Интересно ещё, что is_numeric пропускает любое количество пробельных символов слева, но плохо относится к ним справа:

var_dump(is_numeric("\f\f\t0xBadBabe")); // true
var_dump(is_numeric("1\t")); //false
8 комментариев
Сергей Морозов (morozov.livejournal.com) 2014

В качестве придирки: функция не работает для строк типа «+2» (возвращает FALSE, хотя при приведении к целочисленному типу строка превращается в int(2)). И последняя круглая скобка лишняя.

Евгений Степанищев (bolknote.ru) 2014

Комментарий для morozov.livejournal.com:

Она и не должна работать для строки «+2», с чего бы? Тут ситуация ровно как с нулём — он не должен потеряться, плюс — тоже. Скобку исправил, спасиюо!

steelice.ru 2014

У is_number (особенно если обрабатываются входные данные) не хватает trim().
Причём trim можно использовать только в правой части. Тогда и необходимость явно приводить $var в string отпадает.

Сергей Морозов (morozov.livejournal.com) 2014

Ну если задача функции — убедиться в том, что строка содержит десятичное число, то «+2» — это десятичное число (например, градусов тепла), точно так же, как и «-2» — это десятичное число.

Евгений Степанищев (bolknote.ru) 2014

Комментарий для morozov.livejournal.com:

Вопрос определений. В исходной задачи часто требуется перевести в число то, что должно бы иметь тип int/float, но почему-либо записано в строковой форме (например, пришло из GET/POST-запроса). «+2» тут не может иметь такой тип, так как «плюс» нужно будет восстанавливать из данных (пусть это и делается без проблем), тогда как в строке он просто записан.

Евгений Степанищев (bolknote.ru) 2014

Комментарий для http://steelice.ru:

У is_number (особенно если обрабатываются входные данные) не хватает trim().
Причём trim можно использовать только в правой части. Тогда и необходимость явно приводить $var в string отпадает.

Если вам нужен trim, то очевидно, что пришло не число в строковой форме, а строка, содержащая число и пробельные символы.

Горбунов Олег 2014

Комментарий для Евгения Степанищева:

Если отрицательные значения не нужны, а нужны именно положительные целочисленные значения (например, идентификаторы из базы данных) то есть ctype_digit() (и ctype_xdigit() для тех самых шестнадцатеричных значений)

Евгений Степанищев (bolknote.ru) 2014

Комментарий для Горбунов Олег:

0777 пройдёт ctype_digit().