Summary
Briefly, the function of is_number()
checks type only. Since NAN is a constant with type defined as float, it is returned to true. The function is_float()
returns true
by checking the type of the "object". Theoretically almost the same as is_number()
.
In PHP, the NAN constant is defined as float, according to the documentation: http://php.net/manual/en/math.constants.php. Note that INF (Infinite) is also treated as float.
In function filter_var()
a second check is made after identifying the type, where probably Nan does not pass, returning fake boolean. See below for details on this summary:
Nan is not like Nan
Before demonstrating the source of the functions in question, it is important to understand that it is erroneous to compare NAN with NAN because it represents an undefined state.
var_dump(NAN == NAN); // retorna false
It will never be the same because an undefined state marker is never equal to undefined.
This is because NAN is not a value, it is a position marker (placeholder) for an undefined state. In mathematics, undefined cannot be equal to undefined (undefined != undefined).
To check if it’s a NAN
, use the function is_nan().
Clarified this point, now we go straight to the source to understand what really happens. Below follows the relevant code snippets taken from the official repository: https://github.com/php/php-src
PHP_FUNCTION(is_numeric)
This is the source of the is_numeric function(): https://github.com/php/php-src/blob/master/ext/standard/type.c
The part that matters:
/* {{{ proto bool is_numeric(mixed value)
Returns true if value is a number or a numeric string */
PHP_FUNCTION(is_numeric)
{
zval *arg;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ZVAL(arg)
ZEND_PARSE_PARAMETERS_END();
switch (Z_TYPE_P(arg)) {
case IS_LONG:
case IS_DOUBLE:
RETURN_TRUE;
break;
case IS_STRING:
if (is_numeric_string(Z_STRVAL_P(arg), Z_STRLEN_P(arg), NULL, NULL, 0)) {
RETURN_TRUE;
} else {
RETURN_FALSE;
}
break;
default:
RETURN_FALSE;
break;
}
}
I don’t need to comment on such a simple and obvious code. It’s obvious what happens and why it returns true.
Now note the difference in treatment with function filter_var()
, down below:
void php_filter_float()
This is the function of the PHP interpreter that processes the filter, the function filter_var()
: https://github.com/php/php-src/blob/master/ext/filter/logical_filters.c
I have no suitable environment to test this simple script, but looking superficially it seems that the value falls in this switch()
:
switch (is_numeric_string(num, p - num, &lval, &dval, 0)) {
case IS_LONG:
zval_ptr_dtor(value);
ZVAL_DOUBLE(value, (double)lval);
break;
case IS_DOUBLE:
if ((!dval && p - num > 1 && strpbrk(num, "123456789")) || !zend_finite(dval)) {
goto error;
}
zval_ptr_dtor(value);
ZVAL_DOUBLE(value, dval);
break;
default:
error:
efree(num);
RETURN_VALIDATION_FAILED
}
Nan may be falling in this stretch. Even if he passes as DOUBLE, he may be entering as true in that conditional, which leads to goto error
if ((!dval && p - num > 1 && strpbrk(num, "123456789")) || !zend_finite(dval)) {
goto error;
}
PHP_FUNCTION(is_float)
Now let’s see how the is_float()
deals with the received argument:
static inline void php_is_type(INTERNAL_FUNCTION_PARAMETERS, int type)
{
zval *arg;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ZVAL_DEREF(arg)
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
if (Z_TYPE_P(arg) == type) {
if (type == IS_RESOURCE) {
const char *type_name = zend_rsrc_list_get_rsrc_type(Z_RES_P(arg));
if (!type_name) {
RETURN_FALSE;
}
}
RETURN_TRUE;
} else {
RETURN_FALSE;
}
}
/* {{{ proto bool is_float(mixed var)
Returns true if variable is float point
Warning: This function is special-cased by zend_compile.c and so is usually bypassed */
PHP_FUNCTION(is_float)
{
php_is_type(INTERNAL_FUNCTION_PARAM_PASSTHRU, IS_DOUBLE);
}
Z_TYPE_P()
: https://github.com/php/php-src/blob/master/Zend/zend_types.h
static zend_always_inline zend_uchar zval_get_type(const zval* pz) {
return pz->u1.v.type;
}
/* we should never set just Z_TYPE, we should set Z_TYPE_INFO */
#define Z_TYPE(zval) zval_get_type(&(zval))
#define Z_TYPE_P(zval_p) Z_TYPE(*(zval_p))
In PHP, double, float and decimal is all the same
Some may question that it is wrong to claim that float and double are different because it is known that in PHP the types double and float are treated equally, however, there is a peculiarity. In computational terms, double is different from float as of the type decimal.
Both are stored differently in memory. Other heavily typed languages treat these 3 types as being different.
For further clarification:
https://stackoverflow.com/questions/2386772/difference-between-float-and-double
*There are several other sources. Do not rely only on stackoverflow links.
This subject deviates from the focus of the issue, so I will not go into more detail.
Final consideration
Observing the codes of the 3 functions, is_numeric()
, is_float()
and filter_var()
, We can see that there is no "deep" check. It is merely a matter of dealing with the received argument. While the first two do not check the real value, relying on the typing definition, filter_var()
has a second conditional that perhaps, "accidentally", returns false to the NAN.
We can also observe that the results of both functions have no direct relation with the architecture of the CPU as we could assume.
A relevant fact is, once aware that Nan is always different from Nan, it makes no sense to do any operations with that "object".
For more consistency in codes, if you are dealing with mathematical expressions, apply the function is_nan()
before anything.
This is valid in virtually any language.
Curiosities
echo gettype(NAN); // retorna "double"
var_dump(NAN); //retorna "float(NAN)".
var_dump(empty(NAN)); //retorna "bool(false)".
Excerpt from the source code of gettype()
:
case IS_DOUBLE:
RETVAL_STRING("double");
break;
https://github.com/php/php-src/blob/master/ext/standard/type.c (line 48)
Flames and haters
Referring to unproductive comments made by haters of PHP and flamers.
As mentioned in the paragraphs above, mathematically a value undefined cannot be the same as another undefined. This is Nan’s case.
Some comments on this page imply that this is the arbitrariness of PHP, pejoratively. For the more experienced it is harmless, but it disseminates disinformation to those who do not understand the subject. That is, it disseminates ignorance.
Virtually all programming languages follow the same rule for Nan and define it as float as well.
Yes, I still use PHP, but it pays my salary :p
– Wallace Maxters
PHP being PHP.
– OnoSendai
Question: "Is not-a-number a number?" Answer: "YES!"
– Victor Stafusa
The doc confirm http://php.net/manual/en/function.is-nan.php is float :p ... hard to know why only
– Guilherme Nascimento
@Guilhermenascimento knowing that it is float is one thing, now knowing why two functions evaluate with different results (being that they check the same thing) is hard to accept :\
– Wallace Maxters
Hi, I am PHP.
NAN !== NAN
– Diego Souza
I believe
NaN
be "number" of the typefloat
, but he is "reserved" to be "disregarded". From what I’m reading Nan has a situation that is common in most languages, I will try to understand and answer.– Guilherme Nascimento
Nan believe this :p
– Wallace Maxters
@Onosendai javascript being javascript (
typeof NaN
) ... not defending PHP, just to explain XD– Guilherme Nascimento
@And you happen to think I’d miss the opportunity to mock PHP? ;)
– OnoSendai
@Onosendai PHP will always give you new opportunities :)
– Guilherme Nascimento
Related in English: Why does is_numeric(NAN) Return TRUE?
– Wallace Maxters