在 PHP 个版本中正确处理 NAN

Correctly handing NAN in PHP versions

谁能解释为什么 NAN 和等于 NAN 的变量会根据 PHP 的版本表现不同?

考虑以下代码:

$nan = NAN;
print "PHP Version: " . phpversion(). "\n" .
  '0 <  NAN ? ' . ( 0 < NAN   ? 'TRUE' : 'FALSE' ) . "\n" .
  '0 >  NAN ? ' . ( 0 > NAN   ? 'TRUE' : 'FALSE' ) . "\n" . 
  '0 == NAN ? ' . ( 0 == NAN  ? 'TRUE' : 'FALSE' ) . "\n" . 
  '0 <  $nan ? ' . ( 0 < $nan  ? 'TRUE' : 'FALSE' ) . "\n" .
  '0 >  $nan ? ' . ( 0 > $nan  ? 'TRUE' : 'FALSE' ) . "\n" .
  '0 == $nan ? ' . ( 0 == $nan ? 'TRUE' : 'FALSE' ) . "\n" .
  'is_nan(NAN) ' . ( is_nan(NAN)  ? 'TRUE' : 'FALSE' ) . "\n" .
  'is_nan($nan) ' . ( is_nan($nan) ? 'TRUE' : 'FALSE' ) . "\n" .
  'gettype(NAN) is '  . gettype(NAN)  . "\n" .
  'gettype($nan) is ' . gettype($nan) . "\n";

现在,如果我 运行 此代码针对 PHP 的多个版本(使用 MAMP),结果如下:

PHP Version: 5.3.5
0 <  NAN ? TRUE
0 >  NAN ? TRUE
0 == NAN ? FALSE
0 <  $nan ? TRUE
0 >  $nan ? TRUE
0 == $nan ? FALSE
is_nan(NAN) TRUE
is_nan($nan) TRUE
gettype(NAN) is double
gettype($nan) is double

PHP Version: 5.6.30 (and 5.5.30, 5.4.45)
0 <  NAN ? FALSE
0 >  NAN ? FALSE
0 == NAN ? FALSE
0 <  $nan ? FALSE
0 >  $nan ? FALSE
0 == $nan ? FALSE
is_nan(NAN) TRUE
is_nan($nan) TRUE
gettype(NAN) is double
gettype($nan) is double

PHP Version: 7.1.1 (and 7.0.15)
0 <  NAN ? TRUE
0 >  NAN ? TRUE
0 == NAN ? FALSE
0 <  $nan ? FALSE
0 >  $nan ? FALSE
0 == $nan ? FALSE
is_nan(NAN) TRUE
is_nan($nan) TRUE
gettype(NAN) is double
gettype($nan) is double

PHP 中的函数是否可以依赖于与 NAN 的比较,或者 NAN 是否应该仅与 is_nan() 一起使用?

This bug has now been fixed by this commit. The explanation bellow shows what was causing it.

关于你的第一个问题,似乎自从 PHP7 以来,你会得到不同的结果,具体取决于表达式是在编译期间还是在运行时求值。

首先,重要的是要注意,根据 IEEE754,其中一个元素是 NAN 的所有比较都应该 return false。您可以在 this answer 中找到一些详细信息。考虑到这一点,5.6 的行为似乎是正确的。


因此,对于运行时评估 (0 < $nan),比较是在 VM 中通过 this code:

result = ((double)Z_LVAL_P(op1) < Z_DVAL_P(op2));

该操作会将 0 分配给 result,最终 returned。这将为我们提供 FALSE.

的预期输出

在编译时求值(0 < NAN)的情况下,this code在编译期间完成的比较:

case TYPE_PAIR(IS_LONG, IS_DOUBLE):
    Z_DVAL_P(result) = (double)Z_LVAL_P(op1) - Z_DVAL_P(op2);
    ZVAL_LONG(result, ZEND_NORMALIZE_BOOL(Z_DVAL_P(result)));
    return SUCCESS;

这里还有一些其他的事情发生,减去 0 - NAN = NAN 的结果那个值然后通过 ZEND_NORMALIZE_BOOL 宏,如下所示:

#define ZEND_NORMALIZE_BOOL(n)          \
    ((n) ? (((n)>0) ? 1 : -1) : 0)

如您所见,如果 (n) > 0falseNAN 就是这种情况),宏将 return -1。 然后将该值传递给 is_smaller_function,其中 you'll find:

ZVAL_BOOL(result, (Z_LVAL_P(result) < 0));

鉴于此时 result 成立 -1,这将被评估为 TRUE


关于你的第二个问题,我建议你永远不要依赖与 NAN 的比较并坚持使用 is_nan()。请注意,即使 NAN == NAN 的计算结果也是 false.

这就是 documentation 关于 NaN 的说法:

NaN

Some numeric operations can result in a value represented by the constant NAN. This result represents an undefined or unrepresentable value in floating-point calculations. Any loose or strict comparisons of this value against any other value, including itself, but except TRUE, will have a result of FALSE.

Because NAN represents any number of different values, NAN should not be compared to other values, including itself, and instead should be checked for using is_nan().

用简单的英语来说,NaN 不是一个值。它是一种符号,表示无法使用浮点标准使用实数的内部表示来计算或存储的结果。

NaN 不等于其他任何东西。 NaN 即使与另一个 NaN 也不相等,因为许多不同的计算可以产生 NaN.

检查结果是否为 NaN 的唯一可靠方法是使用 is_nan() 函数。

您的代码中只有最后四个测试有效:

is_nan(NAN) TRUE
is_nan($nan) TRUE
gettype(NAN) is double
gettype($nan) is double