Why is the NAN constant evaluated as True when testing it with is_numeric?

Asked

Viewed 1,270 times

13

I was doing a test with the constant NAN in PHP, because until then I had never used it. I did tests out of curiosity.

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

Behold

 is_numeric(NAN); // bool(true)

Proof in IDEONE

What I found controversial is that I learned here on the site that Nan means "Not a number". And just the function called is_numeric returns True ???

Well, is there any theory to explain this? Or would this be some function bug.

If NAN means "Not a number", like the function is_numeric could return positive for NAN?

Why the result of is_numeric(NAN) is true?

Updating

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

is_float(NAN) // true
filter_var(NAN, FILTER_VALIDATE_FLOAT)// false
  • Yes, I still use PHP, but it pays my salary :p

  • 11

    PHP being PHP.

  • 3

    Question: "Is not-a-number a number?" Answer: "YES!"

  • The doc confirm http://php.net/manual/en/function.is-nan.php is float :p ... hard to know why only

  • @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 :\

  • 2

    Hi, I am PHP. NAN !== NAN

  • I believe NaN be "number" of the type float, 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.

  • 1

    Nan believe this :p

  • 1

    @Onosendai javascript being javascript (typeof NaN) ... not defending PHP, just to explain XD

  • 2

    @And you happen to think I’d miss the opportunity to mock PHP? ;)

  • 1

    @Onosendai PHP will always give you new opportunities :)

Show 7 more comments

2 answers

11

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.

inserir a descrição da imagem aqui

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.

  • Wrong. double and float in PHP is the same thing. Example

  • 1

    var_dump((double)1.5 === (float) 1.5); // true

  • I was hoping someone would say that, but then I caught you rsr.. Actually not Wallace, because float is different from double. Both are stored differently in memory. That’s the difference. But since that is not the focus of the issue, so I avoided diverting the subject to this side.

  • Actually not Wallace, because float is different from double. Both are stored differently. That’s the difference.

  • 2

    And the is_numeric stays where?

  • 2
  • is_numeric is the same as is_float(), does not compare type.

  • 1

    Inclusive the documentation says that doubleval is alias for floatval. Differs?

  • Here’s Wallace, in php, decimal, double and float is all treated like float. the old functions were written that way. It’s that simple. But the filter_var() function brought more consistency in checking the "real" type.

  • 1

    Take a strongly typed language, for example, Objective-C. In this language, float, decimal and double are different types. PHP, at the beginning of its development, probably wanted to "summarize" and treat everything as float. But anyway, before leaving negative, do a search at least.. rsrrsr by the way are basic subjects of computing...

  • The only difference is this: Float variables require 4 bytes of memory to be stored, already double require 8 bytes. The utility is the same.

  • Mauro, exact.. that’s how PHP interpreted it, the utility, the functionality.. but in terms of storage are different.

  • 1

    With the editing and explanation of the functions, the answer was very good. + 1

Show 8 more comments

6


The 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 is more a matter of "low level" (or "lower level"), it is something like the processor works (read about FPU), that is to say NaN uses 0.0 because it is an "unused" point, see the description of wiki:

In computing, Nan, Standing for not a number, is the Numeric data type value Representing an Undefined or unrepresentable value, especially in floating-point calculations.

It would be something like:

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

And another excerpt from Wikipedia:

Most floating point systems use representations defined in the standard IEEE 754.

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

That is to say This is not a PHP problem and yes how the processor/compiler will work.

A note, in PHP float, double and "real numbers" have the same type of data, 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 usually:

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.11e-16. Non Elementary arithmetic Operations may Give Larger errors, and, of Course, error Propagation must be considered when several Operations are compounded.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.