PHP における NaN とは

2021-03-30
PHP
NaN

PHP の型について調べていたら NAN に関する記述を見つけて、これは何?と思ったので調査。

PHP: Floating point numbers - Manual

※なお、このエントリ内の PHP ソースコードの実行は、PHP 8.0.3 を使っています。

NaN とは

NaN - Wikipedia

Not a Number の略です。浮動小数点計算の技術標準 IEEE 754 にて仕様が定められています。PHP も typically uses the IEEE 754 double precision format と書かれています。typically が気になるところではあります。

PHPマニュアルにおける 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 に対する比較は、true との比較以外は false を返すとなっています。

試してみると、厳密比較は false でしたが緩やかな比較では true となりました。良し悪しはおいておいて、そういう仕様ということです。

1
2
3
4
php > var_dump(NAN == true);
bool(true)
php > var_dump(NAN === true);
bool(false)

NaN を出力するコード

今までの PHP プログラマー人生では一度も使ったことがなかったです。JavaScript の NaN は有名ですが、PHP の NaN は今回が初見です。というわけで、いくつか NaN を出力するサンプルコードを書いてみました。

1
2
3
4
php > var_dump(sqrt(-1));
float(NAN)
php > var_dump(log(tan(3.14)));
float(NAN)

NaN の使いどころ

用途としては、浮動小数点計算を行った際に、計算結果が表現不能になっていないか?など検査するときでしょうか?is_nan という検査メソッドも用意されているので、次のような利用方法が考えられそうです。

1
2
3
4
5
6
<?php
$result = some_calulation($a, $b);

if( is_nan($result) ){
// do something
}

まとめ

浮動小数点計算における未定義、計算不可能を表すのが NaN ということがよくわかりました。あまりこの手の処理をやったことがないので知らなかったのですが、数学の分野であったり、研究結果の計算などではよく使うのかもしれません。仕事として NaN を意識することが今後あるのか…は、よく分からないですが、知っておくと役に立つ知識であることは間違いないです。

最初は桁溢れの表現かなと勘違いしていましたが、それは INF という定数が用意されていました。is_infinite という検査メソッドも存在しています。

おまけ

この計算は、どう NaN の?

四則演算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
php > var_dump(NAN + NAN);
float(NAN)
php > var_dump(NAN - NAN);
float(NAN)
php > var_dump(NAN * NAN);
float(NAN)
php > var_dump(NAN ** NAN);
float(NAN)
php > var_dump(NAN / NAN);
float(NAN)
php > var_dump(NAN % NAN);
PHP Warning: Uncaught DivisionByZeroError: Modulo by zero in php shell code:1
Stack trace:
#0 {main}
thrown in php shell code on line 1

NAN / NANNAN なのに、NAN % NAN は0除算で怒られる…。

Bit演算

1
2
3
4
5
6
7
8
9
10
11
12
php > var_dump(NAN & NAN);
int(0)
php > var_dump(NAN | NAN);
int(0)
php > var_dump(NAN ^ NAN);
int(0)
php > var_dump(~NAN);
int(-1)
php > var_dump(NAN << NAN);
int(0)
php > var_dump(NAN >> NAN);
int(0)

NaN を出す PHP のソースコード

いっぱいあったのですが、 対数計算のソースコードを選んで見ました。対数の定義では底は1でない整数です。実際にソースコードにおいても底が1の場合は、計算不能を表す ZEND_NAN を返すようになっています。

https://github.com/php/php-src/blob/master/ext/standard/math.c#L571-L604

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
PHP_FUNCTION(log)
{
double num, base = 0;

ZEND_PARSE_PARAMETERS_START(1, 2)
Z_PARAM_DOUBLE(num)
Z_PARAM_OPTIONAL
Z_PARAM_DOUBLE(base)
ZEND_PARSE_PARAMETERS_END();

if (ZEND_NUM_ARGS() == 1) {
RETURN_DOUBLE(log(num));
}

if (base == 2.0) {
RETURN_DOUBLE(log2(num));
}

if (base == 10.0) {
RETURN_DOUBLE(log10(num));
}

if (base == 1.0) {
RETURN_DOUBLE(ZEND_NAN);
}

if (base <= 0.0) {
zend_argument_value_error(2, "must be greater than 0");
RETURN_THROWS();
}

PHPで実行してみた結果

1
2
php > var_dump(log(1,1));
float(NAN)