各種is_...関数の挙動の比較
PHPで入力値や計算値が数値かどうかを判断するときにどの関数を使ったらいいの? と思って調べたのだが、明確な答えが見つからなかったので自分でやってみた。結果、想像していた通りになったりならなかったりして面白かったので紹介したい。エントリーメンバーはこちら。
-
is_nan(float $num): bool
値がNAN
であればTRUE
を返す、と言われるけれども、NAN
そのものが「(float
型の)数」なのに「Not A Number
(非数)」という難解な説明。今回は数値かどうかの判断なので!is_nan()
で検証。引数にfloat
でない値(例えば文字列)を入れるとエラーを出す(はずなのだが条件がある。後述)。 -
is_numeric(mixed $value) :bool
「数か数値形式の文字列」(≒数っぽい値)であればTRUEを返す、という<form>
の値を受ける時毎度お世話になる関数1。 -
is_int(mixed $value) :bool
マニュアル通りに解釈すると「変数が整数型であればTRUE
」となるが、mixed
を引くので文字列を入れてもエラーにはならずFALSE
を返す。 -
is_float(mixed $value :bool
上のis_int()
と逆に「変数が浮動小数点型であればTRUE
」。ちなみにPHP_INT_MAX
を超える数や指数表記の数は結果が整数であっても浮動小数点型になる。 -
is_finite(float $num) :bool
数値判定を何でやれば…?をググってよく出てくるのはコレ2。「有限の浮動小数点値であればTRUE
」であり、「有限の浮動小数点値」とは「浮動小数点がNAN
でなくかつ無限(INF
)でもない」とマニュアルにはあるが、じゃあ整数値を入れるとどうなるのか、という疑問が残る。
さて、この関数群に様々な値をぶち込んだ結果がこちら。
数の説明 | !is_nan | is_numeric | is_int | is_float | is_finite | |
---|---|---|---|---|---|---|
0 | 数値のゼロ | TRUE | TRUE | TRUE | FALSE | TRUE |
10**100 | PHP_INT_MAXを 超えるint |
TRUE | TRUE | FALSE | TRUE | TRUE |
sqrt(-1) | √-1(虚数) | FALSE | TRUE | FALSE | TRUE | FALSE |
tan(pi()/2) | tan(π/2)(数学で は未定義) |
TRUE | TRUE | FALSE | TRUE | TRUE |
log(0) | 0の自然対数(数 学では未定義) |
TRUE | TRUE | FALSE | TRUE | FALSE |
1.5E2 | intの範囲の 指数表記 |
TRUE | TRUE | FALSE | TRUE | TRUE |
1.5E10000 | 64bitの限界を 超える指数表記 |
TRUE | TRUE | FALSE | TRUE | FALSE |
0b1101101 | int(2進表記) | TRUE | TRUE | TRUE | FALSE | TRUE |
0155 | int(8進表記) | TRUE | TRUE | TRUE | FALSE | TRUE |
0x6D | int(16進表記) | TRUE | TRUE | TRUE | FALSE | TRUE |
'0' | 文字列のゼロ | TRUE | TRUE | FALSE | FALSE | TRUE |
'1.5E2' | 指数表記の 文字列 |
TRUE | TRUE | FALSE | FALSE | TRUE |
'0155' | 8進表記の 文字列 |
TRUE | TRUE | FALSE | FALSE | TRUE |
'0x6D' | 16進表記の 文字列 |
ERROR | FALSE | FALSE | FALSE | ERROR |
INF | 無限大 | TRUE | TRUE | FALSE | TRUE | FALSE |
NAN | 非数 | FALSE | TRUE | FALSE | TRUE | FALSE |
以下、結果を見てあれ? と思ったところ。
-
is_finite(0) : TRUE
一見すると正しいが、0は浮動小数点値ではない(その証拠にis_float(0) : FALSE
である)のに、is_finite()
の定義「有限の浮動小数点値であればTRUE
」から見ると結果が正しくない。むしろ定義のほうを「整数値または有限の浮動小数点値であればTRUE
」とするべきであろうか。 -
is_float(sqrt(-1)) : TRUE
is_nan(sqrt(-1)) : TRUE
はNAN
の例としてよく挙げられているが、これは「非数というfloat
の一種」という意味らしい。実際var_dump(sqrt(-1))
の返り値はfloat(NAN)
である。 -
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
と判定されるようだ。 -
is_finite(log(0)) : FALSE
上と同じ数学上の未定義値だがなぜかこちらはis_finite()
がFALSE
で!is_nan()
がTRUE
。これも種明かしするとvar_dump(log(0)) : -INF
、無限(INF, -INF
)は非数ではないのでこの結果になる。 -
!is_nan('0') : TRUE
is_nan()
は引数に文字列を入れるとTypeError
を出すはずなのになぜか'0'
はそのまま通してしまった。同様のことがis_finite()
でも起こっている。後の'1.5E'
と'0155'
を見るに「数値形式の文字列」(is_numeric()
がTRUE
を返す値)であれば数値と解釈してくれるようだ5。
数値と文字列を厳密に区別する方法
ところで'0'を数値としたくない(0と'0'を区別したい)ときに、2つ思いついたのを同じように比較してみた。
is_int(\$a) || is_float(\$a) | intval(\$a)===\$a || floatval(\$a)===\$a | |
---|---|---|
0 | TRUE | TRUE |
10**100 | TRUE | TRUE |
sqrt(-1) | TRUE | FALSE |
tan(pi()/2) | TRUE | TRUE |
log(0) | TRUE | TRUE |
1.5E2 | TRUE | TRUE |
1.5E10000 | TRUE | TRUE |
0b1101101 | TRUE | TRUE |
0155 | TRUE | TRUE |
0x6D | TRUE | TRUE |
'0' | FALSE | FALSE |
'1.5E2' | FALSE | FALSE |
'0155' | FALSE | FALSE |
'0x6D' | FALSE | FALSE |
INF | TRUE | TRUE |
NAN | TRUE | FALSE |
どちらも数値形式の文字列はちゃんと弾いてくれる。また、上で述べたようにsqrt(-1)
はNAN
なのだが、phpマニュアルのis_nanの項目の警告として
NAN は、 NAN と等しい値として比較できません。 浮動小数点数の値が NAN かを判定するには is_nan() を使わなければいけません。 コード $float === NAN は動作しません。
とあるようにfloatval(NAN) === NAN
はFALSE
である一方、is_float(NAN)
はTRUE
なのでこの部分だけ結果が異なる。
思いつきでやってみたけど、いろいろ勉強になった。
-
PHPは周知の通り型判定がゆるいので
$a = '5'; $b = 6; var_dump($a * $b) : int(30)
とかいう、他言語ではありえない結果になる。しかし例えばJavaScriptなどではそうならないため、intval()
をかまさないと思ったようにならなかったりする(何度ハマったことか…)。 ↩ -
英語の「finite」は「ファイナイト」と発音するので念の為。infiniteは「インフィニット」である。 ↩
-
is_nan(1/0)
やis_infinite(1/0)
を実行するとDivisionByZeroError
になりTRUE
もFALSE
も返さない。 ↩ -
この妙な結果は、正接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)
を実行しても同じ結果になる。 ↩ -
これはマニュアルには書かれていない。なお定義上の「数値形式の文字列」に指数(E表記)は含まれているが2進・16進表記は含まれていないので、
is_numeric('0x6D')
はFALSE
となり、!is_nan('0x6D')
がTypeError
になるという仕様。 ↩