Why is the constant NAN evaluated as true when testing it with is numeric?

I was doing a test with the constant NAN in PHP, since until then I had never used the same. I ran tests out of curiosity.

I noticed something interesting. When calling the function is_numeric in NAN the result was TRUE.

See

 is_numeric(NAN); // bool(true)

To test No IDEONE

What I found contradictory is that I learned here on the site that NaN means "Not a number". And precisely the function called is_numeric returns True ???

Well, Is there any theory to explain this? Or that would be some bug of the function.

If NAN means "not a number", how could the function is_numeric return positive for NAN?

Why is the result of is_numeric(NAN) true?

Update

To further increase the discussion, two other tests revealed contradictory results:

is_float(NAN) // true
filter_var(NAN, FILTER_VALIDATE_FLOAT)// false
Author: Comunidade, 2016-11-11

2 answers

O NAN (NAN or -NAN) is a non-zero value with a floating point, i.e. 0.0, which is different from 0

This is due to the level it is more a matter of " low level "(or "lower level"), it is something in how the processor works (read about FPU) , ie NaN uses 0.0 since it is an "unused" point, see the description of the wiki :

In computing, NaN, standing for not a number , is a numerical data type value representing an undefined or unrepresentable value, especially in floating-point calculations.

Would be something like:

NaN is a value of the numeric type that represents an undefined value or that cannot be represented, especially in floating point calculations.

And another excerpt from Wikipedia :

Most systems operating with floating point Use Representations defined in the IEEE standard 754.

The IEEE standard for floating point arithmetic (IEEE 754) is the most widely used standard for floating point calculation, and is followed by many CPUs and FPU improvements. The standard defines formats for representing floating point numbers (including zero) and non-normalized values, as well as infinity and Nan special values, with a set of floating point operations that work with these values. It also specifies four rounding modes and five exceptions (including when these exceptions occur is what happens at these times).

I.e. this is not a PHP problem but how the processor/compiler will work.

A note , in PHP float, double and "real numbers" have the same data type as explained in the documentation http://www.php.net/manual/en/language.types.float.php :

<?php
$a = 1.234; 
$b = 1.2e3; 
$c = 7E-10;

Only at the "lowest level" are they stored as double generally:

Floating point numbers have limited precision. Although it depends on the system, PHP typically uses the IEEE 754 double precision format, which will give a maximum relative error due to rounding in the order of 1.11 e-16. Non elementary arithmetic operations may give larger errors, and, of course, error propagation must be considered when several operations are composed.

 6
Author: Guilherme Nascimento, 2016-11-14 13:07:45

Summary

Briefly, the function is_number() checks the type only. Since NAN is a constant with type set to float, it is returned as true. The function is_float() returns true by checking the type of the "object". Theoretically almost the same as is_number().


in PHP, the constant NAN 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 .

insert the description of the image here

In the function filter_var() a second check is done after identifying the type, where the Nan probably does not pass, returning false boolean. See below for a breakdown of this summary:

NaN is not equal to 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 NAN represents an undefined state.

var_dump(NAN == NAN); // retorna false

Will never be equal because a marker of undefined state is never equal to undefined.

This happens because NAN is not a value, it is a placeholder for an undefined state. In mathematics, indefinite cannot be equal to indefinite (indefinite != undefined).

To check if it is a NAN, use the function is_nan().

Clarified that point, now let's go straight to the source to understand what actually happens. Below is 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 function is_numeric (): https://github.com/php/php-src/blob/master/ext/standard/type.c

The excerpt 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 simple and obvious code. It's obvious what happens and why it returns true.

Now note the difference in treatment with the function filter_var(), below:

Void php_filter_float ()

This is the function of the PHP interpreter that processes the filter, of the function filter_var(): https://github.com/php/php-src/blob/master/ext/filter/logical_filters.c

I'm out of a proper environment to test this simple script, but looking superficially it looks like the value falls on that 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
    }

The NaN may be falling in this stretch. Even if it passes as DOUBLE, it 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 is_float() handles 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 equal

Some may question saying that it is wrong to state that float and double are different because it is known that in PHP the types double and float are treated equally, but, there is a peculiarity. In computational terms, double is different from float such as the type decimal. Both are stored differently in memory. Other strongly 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 links from stackoverflow. This topic deviates from the focus of the issue, so I will not dwell on it in more detail.

Final consideration

Looking at 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 handling the received argument. While the first two do not check the real value, relying on the typesetting definition, filter_var() has a second conditional that perhaps "accidentally" returns false to NAN.

We can also observe that the results of both functions have no direct relationship to the CPU architecture as we might assume.

A relevant fact is, once aware that NaN is always different from NaN, it makes no sense to do any operations with this "object".

For greater code consistency, if you are dealing with mathematical expressions, apply the is_nan() function before anything else.
this holds true in virtually any language.

Curiosities

echo gettype(NAN); // retorna "double"
var_dump(NAN); //retorna "float(NAN)".
var_dump(empty(NAN)); //retorna "bool(false)".

Source code excerpt from 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 PHP's haters and flamers .

As mentioned in the paragraphs above, mathematically one indefinite value cannot be equal to another indefinite . This is NaN's case. Some comments on this page suggest that it is PHP's arbitrariness, pejoratively. For the more experienced it is harmless, however, it spreads misinformation to those who do not understand the subject. That is, it spreads ignorance.
virtually all programming languages follow the same rule for NaN and define it as float as well.

 11
Author: Daniel Omine, 2017-05-23 12:37:27