PHPの数値判定を色々なis...関数で試した結果 (一覧表あり)

#### 各種is_...関数の挙動の比較
PHPで入力値や計算値が数値かどうかを判断するときにどの関数を使ったらいいの？　と思って調べたのだが、明確な答えが見つからなかったので自分でやってみた。結果、想像していた通りになったりならなかったりして面白かったので紹介したい。エントリーメンバーはこちら。

1. ```is_nan(float $num): bool```
値が```NAN```であれば```TRUE```を返す、と言われるけれども、```NAN```そのものが「(```float```型の)数」なのに「```Not A Number```(非数)」という難解な説明。今回は数値かどうかの判断なので```!is_nan()```で検証。引数に```float```でない値(例えば文字列)を入れるとエラーを出す(はずなのだが条件がある。後述)。
2. ```is_numeric(mixed $value) :bool```
「数か[数値形式の文字列](https://www.php.net/manual/ja/language.types.numeric-strings.php)」(≒数っぽい値)であればTRUEを返す、という```<form>```の値を受ける時毎度お世話になる関数[^1]。
3. ```is_int(mixed $value) :bool```
マニュアル通りに解釈すると「変数が整数型であれば```TRUE```」となるが、```mixed```を引くので文字列を入れてもエラーにはならず```FALSE```を返す。
4. ```is_float(mixed $value :bool```
上の```is_int()```と逆に「変数が浮動小数点型であれば```TRUE```」。ちなみに```PHP_INT_MAX```を超える数や指数表記の数は結果が整数であっても浮動小数点型になる。
5. ```is_finite(float $num) :bool```
数値判定を何でやれば…？をググってよく出てくるのはコレ[^2]。「有限の浮動小数点値であれば```TRUE```」であり、「有限の浮動小数点値」とは「浮動小数点が```NAN```でなくかつ無限(```INF```)でもない」とマニュアルにはあるが、じゃあ整数値を入れるとどうなるのか、という疑問が残る。

さて、この関数群に様々な値をぶち込んだ結果がこちら。
<table>
<thead>
<tr><th></th><th>数の説明</th><th>!is_nan&nbsp;</th><th>is_numeric</th><th>is_int&nbsp;&nbsp;</th><th>is_float&nbsp;</th><th>is_finite&nbsp;</th></tr>
</thead>
<tbody>
<tr><td>0</td><td>数値のゼロ</td><td>TRUE</td><td>TRUE</td><td>TRUE</td><td>FALSE</td><td>TRUE</td></tr>
<tr><td>10**100</td><td>PHP_INT_MAXを<br>超えるint</td><td>TRUE</td><td>TRUE</td><td>FALSE</td><td>TRUE</td><td>TRUE</td></tr>
<tr><td>sqrt(-1)</td><td>√-1(虚数)</td><td>FALSE</td><td>TRUE</td><td>FALSE</td><td>TRUE</td><td>FALSE</td></tr>
<tr><td nowrap>tan(pi()/2)</td><td>tan(π/2)(数学で<br>は未定義)</td><td>TRUE</td><td>TRUE</td><td>FALSE</td><td>TRUE</td><td>TRUE</td></tr>
<tr><td nowrap>log(0)</td><td>0の自然対数(数<br>学では未定義)</td><td>TRUE</td><td>TRUE</td><td>FALSE</td><td>TRUE</td><td>FALSE</td></tr>
<tr><td>1.5E2</td><td>intの範囲の<br>指数表記</td><td>TRUE</td><td>TRUE</td><td>FALSE</td><td>TRUE</td><td>TRUE</td></tr>
<tr><td>1.5E10000</td><td>64bitの限界を<br>超える指数表記</td><td>TRUE</td><td>TRUE</td><td>FALSE</td><td>TRUE</td><td>FALSE</td></tr>
<tr><td>0b1101101</td><td>int(2進表記)</td><td>TRUE</td><td>TRUE</td><td>TRUE</td><td>FALSE</td><td>TRUE</td></tr>
<tr><td>0155</td><td>int(8進表記)</td><td>TRUE</td><td>TRUE</td><td>TRUE</td><td>FALSE</td><td>TRUE</td></tr>
<tr><td>0x6D</td><td>int(16進表記)</td><td>TRUE</td><td>TRUE</td><td>TRUE</td><td>FALSE</td><td>TRUE</td></tr>
<tr><td>'0'</td><td>文字列のゼロ</td><td>TRUE</td><td>TRUE</td><td>FALSE</td><td>FALSE</td><td>TRUE</td></tr>
<tr><td>'1.5E2'</td><td>指数表記の<br>文字列</td><td>TRUE</td><td>TRUE</td><td>FALSE</td><td>FALSE</td><td>TRUE</td></tr>
<tr><td>'0155'</td><td>8進表記の<br>文字列</td><td>TRUE</td><td>TRUE</td><td>FALSE</td><td>FALSE</td><td>TRUE</td></tr>
<tr><td>'0x6D'</td><td>16進表記の<br>文字列</td><td>ERROR</td><td>FALSE</td><td>FALSE</td><td>FALSE</td><td>ERROR</td></tr>
<tr><td>INF</td><td>無限大</td><td>TRUE</td><td>TRUE</td><td>FALSE</td><td>TRUE</td><td>FALSE</td></tr>
<tr><td>NAN</td><td>非数</td><td>FALSE</td><td>TRUE</td><td>FALSE</td><td>TRUE</td><td>FALSE</td></tr>
</tbody>
</table>

以下、結果を見てあれ？　と思ったところ。
1. ```is_finite(0) : TRUE```
一見すると正しいが、0は浮動小数点値ではない(その証拠に```is_float(0) : FALSE```である)のに、```is_finite()```の定義「有限の浮動小数点値であれば```TRUE```」から見ると結果が正しくない。むしろ定義のほうを「整数値または有限の浮動小数点値であれば```TRUE```」とするべきであろうか。
2. ```is_float(sqrt(-1)) : TRUE```
```is_nan(sqrt(-1)) : TRUE```は```NAN```の例としてよく挙げられているが、これは「非数という```float```の一種」という意味らしい。実際```var_dump(sqrt(-1))```の返り値は```float(NAN)```である。
3. ```is_finite(tan(pi()/2)) : TRUE```
数学でのtan(π/2)は0除算を含むため未定義であるが、PHPではエラーが出ないどころか```INF```にすらならず```is_finite()```や```!is_nan()```が```TRUE```になる[^3]。このタネは```tan(pi()/2)```の値にあって、```var_dump(tan(pi()/2))```を実行するとなんと```float(16331239353195370)```という値[^4]が返ってくる。要するに単なる有限値の```float```と判定されるようだ。
4. ```is_finite(log(0)) : FALSE```
上と同じ数学上の未定義値だがなぜかこちらは```is_finite()```が```FALSE```で```!is_nan()```が```TRUE```。これも種明かしすると```var_dump(log(0)) : -INF```、無限(```INF, -INF```)は非数ではないのでこの結果になる。
5. ```!is_nan('0') : TRUE```
```is_nan()```は引数に文字列を入れると```TypeError```を出すはずなのになぜか```'0'```はそのまま通してしまった。同様のことが```is_finite()```でも起こっている。後の```'1.5E'```と```'0155'```を見るに「数値形式の文字列」(```is_numeric()```が```TRUE```を返す値)であれば数値と解釈してくれるようだ[^5]。

#### 数値と文字列を厳密に区別する方法
ところで'0'を数値としたくない(0と'0'を区別したい)ときに、2つ思いついたのを同じように比較してみた。

<table>
<thead>
<tr><th></th><th>is_int(\$a) || is_float(\$a)</th><th>intval(\$a)===\$a || floatval(\$a)===\$a</th>
</thead>
<tbody>
<tr><td>0</td><td>TRUE</td><td>TRUE</td></tr>
<tr><td>10**100</td><td>TRUE</td><td>TRUE</td></tr>
<tr><td>sqrt(-1)</td><td>TRUE</td><td>FALSE</td></tr>
<tr><td nowrap>tan(pi()/2)</td><td>TRUE</td><td>TRUE</td></tr>
<tr><td nowrap>log(0)</td><td>TRUE</td><td>TRUE</td></tr>
<tr><td>1.5E2</td><td>TRUE</td><td>TRUE</td></tr>
<tr><td>1.5E10000</td><td>TRUE</td><td>TRUE</td></tr>
<tr><td>0b1101101</td><td>TRUE</td><td>TRUE</td></tr>
<tr><td>0155</td><td>TRUE</td><td>TRUE</td></tr>
<tr><td>0x6D</td><td>TRUE</td><td>TRUE</td></tr>
<tr><td>'0'</td><td>FALSE</td><td>FALSE</td></tr>
<tr><td>'1.5E2'</td><td>FALSE</td><td>FALSE</td></tr>
<tr><td>'0155'</td><td>FALSE</td><td>FALSE</td></tr>
<tr><td>'0x6D'</td><td>FALSE</td><td>FALSE</td></tr>
<tr><td>INF</td><td>TRUE</td><td>TRUE</td></tr>
<tr><td>NAN</td><td>TRUE</td><td>FALSE</td></tr>
</tbody>
</table>

どちらも数値形式の文字列はちゃんと弾いてくれる。また、上で述べたように```sqrt(-1)```は```NAN```なのだが、[phpマニュアルのis_nanの項目](https://www.php.net/manual/ja/function.is-nan.php)の警告として
> __NAN__ は、 __NAN__ と等しい値として比較できません。 浮動小数点数の値が __NAN__ かを判定するには __is_nan()__ を使わなければいけません。 コード $float === NAN は動作しません。

とあるように```floatval(NAN) === NAN```は```FALSE```である一方、```is_float(NAN)```は```TRUE```なのでこの部分だけ結果が異なる。

思いつきでやってみたけど、いろいろ勉強になった。

[^1]:PHPは周知の通り型判定がゆるいので```$a = '5'; $b = 6; var_dump($a * $b) : int(30)```とかいう、他言語ではありえない結果になる。しかし例えばJavaScriptなどではそうならないため、```intval()```をかまさないと思ったようにならなかったりする(何度ハマったことか…)。
[^2]:英語の「finite」は「ファイナイト」と発音するので念の為。infiniteは「インフィニット」である。
[^3]:```is_nan(1/0)```や```is_infinite(1/0)```を実行すると```DivisionByZeroError```になり```TRUE```も```FALSE```も返さない。
[^4]:この妙な結果は、正接tan(α)をsin(α)/cos(α)によって算出していることから来ているようだ。円周率が途中で丸められてしまうためcos(π/2)=0.000 000 000 000 000 061 232 339 957 367 66となり(0にはならない)、これでsin(π/2)=1を割ると17桁の謎整数が出てくるらしい。なおJavaScriptで```Math.tan(Math.PI()/2)```を実行しても同じ結果になる。
[^5]:これはマニュアルには書かれていない。なお定義上の「数値形式の文字列」に指数(E表記)は含まれているが2進・16進表記は含まれていないので、```is_numeric('0x6D')```は```FALSE```となり、```!is_nan('0x6D')```が```TypeError```になるという仕様。