PHPで数値か判定する方法 - 数値に関する検証を添えて[php]
検証なんてどうでもいいよって人が多い気もするので結論から書く。
PHPで与えられた値が数字のみの値であるか判定する場合
bool ctype_digit("{$var}")
を個人的に一番推す。あくまで個人的にだぞ!
引数を文字列化してるのがミソ。
さて、ここからが本題。
PHP触ってるとあれ?っと思った挙動が結構ある。なんというかポリシーなんてくそくらえだぜ!!みたいなノリで実装されてるんじゃないかといったさ。最近数値っぽい文字列あつかってるときにそういった挙動があったので、せっかくだからちょっと調べてみた。
ちなみに最後の方に環境情報書いてるけどPHP5.4.11だよ。
検証対象とする値
普通の数値リテラルとしての値。2進数、8進数、16進数は省略。
- 100
- 1000
- 10.01
- 0
- -100
- -1000
文字列リテラルとしての値。この辺りは普通。
- "100"
- "1000"
- "10.01"
- "0"
- "-100"
- "-1000"
- "99999999999999999999999999999999999999"
空文字と文字列の前後に空白を入れたパターン
- ""
- " 100 "
2進数, 8進数, 16進数、指数表記を文字列として含めたパターン。通常の整数リテラルで2進数が可能になったのはphp5.4.0から。
- "0b10" (10進数の2)
- "0b12" (2進数としておかしい)
- "010" (10進数の8)
- "019" (8進数としておかしい)
- "0xFF" (10進数の255)
- "0xFFG"(16進数としておかしい)
- "1e2" (10進数の100)
- "1e+2" (10進数の100)
- "1e2e1"(指数表記としておかしい)
上記の値で検証を行う。
検証パターン
正規表現なので特に数値と関係なくどうとでもなる。ただの参考値として。
- preg_match
今回は preg_match('/^-?\d+(\.\d+)?$/', $var) とする。
検証を行う系の関数。bool値を返す。
- is_numeric
- ctype_digit
- is_int
- is_float
ちょっとトリッキーだがフィルタ系を検証に使う。
フィルタリングされたデータ、処理に失敗した場合に FALSE を返す。
- filter_var(FILTER_VALIDATE_INT)
- filter_var(FILTER_VALIDATE_FLOAT)
~化する系の関数。戻り値を見てみる。
- intval
- floatval
最後に関数と言うわけではなく、演算処理
- $var + 0
検証結果
個人的にハマりやすそうだと思ったところに色をつけてみた。
preg_match | is_numeric | ctype_digit | is_int | is_float | |
100 | 1 | true | false | true | false |
1000 | 1 | true | true | true | false |
10.01 | 1 | true | false | false | true |
0 | 1 | true | false | true | false |
-100 | 1 | true | false | true | false |
-1000 | 1 | true | false | true | false |
"100" | 1 | true | true | false | false |
"1000" | 1 | true | true | false | false |
"10.01" | 1 | true | false | false | false |
"0" | 1 | true | true | false | false |
"-100" | 1 | true | false | false | false |
"-1000" | 1 | true | false | false | false |
"99999999999999999999999" | 1 | true | true | false | false |
"" | 0 | false | false | false | false |
" 100 " | 0 | false | false | false | false |
"0b10" | 0 | false | false | false | false |
"0b12" | 0 | false | false | false | false |
"010" | 1 | true | true | false | false |
"019" | 1 | true | true | false | false |
"0xFF" | 0 | true | false | false | false |
"0xFFG" | 0 | false | false | false | false |
"1e2" | 0 | true | false | false | false |
"1e+2" | 0 | true | false | false | false |
"1e2e1" | 0 | false | false | false | false |
filter_var(int) | filter_var(float) | intval | floatval | $var+0 | |
100 | 100 | 100 | 100 | 100 | 100 |
1000 | 1000 | 1000 | 1000 | 1000 | 1000 |
10.01 | false | 10.01 | 10 | 10.01 | 10.01 |
0 | 0 | 0 | 0 | 0 | 0 |
-100 | -100 | -100 | -100 | -100 | -100 |
-1000 | -1000 | -1000 | -1000 | -1000 | -1000 |
"100" | 100 | 100 | 100 | 100 | 100 |
"1000" | 1000 | 1000 | 1000 | 1000 | 1000 |
"10.01" | false | 10.01 | 10 | 10.01 | 10.01 |
"0" | 0 | 0 | 0 | 0 | 0 |
"-100" | -100 | -100 | -100 | -100 | -100 |
"-1000" | -1000 | -1000 | -1000 | -1000 | -1000 |
"99999999999999999999999" | false | 1.0E+23 | PHP_INT_MAX | 1.0E+23 | 1.0E+23 |
"" | false | false | 0 | 0 | 0 |
" 100 " | 100 | 100 | 100 | 100 | 100 |
"0b10" | false | false | 0 | 0 | 0 |
"0b12" | false | false | 0 | 0 | 0 |
"010" | false | 10 | 10 | 10 | 10 |
"019" | false | 19 | 19 | 19 | 19 |
"0xFF" | false | false | 0 | 0 | 255 |
"0xFFG" | false | false | 0 | 0 | 255 |
"1e2" | false | 100 | 1 | 100 | 100 |
"1e+2" | false | 100 | 1 | 100 | 100 |
"1e2e1" | false | false | 1 | 100 | 100 |
挙動まとめ
is_numeric
- かなり範囲が広く、純粋に数値が入力されたか?といった判定では使いづらい印象。
- 16進数表記、指数表記は数値と判定されるのに2進数表記は数値として判定されない。
ctype_digit
is_int, is_float
- すごく名前に忠実で変数をgettypeした時がintegerとかfloatかを判定しているだけ。
filter_var
- 指数表記はfloatとみなす。
- なぜか"010"がintフィルターだとfalse判定。かと思ったらfloatフィルターだと正しいらしい。但し10進数で返ってくる。
- ポリシーがよくわからない。
intval, floatval
- 大体イメージ通りだが指数表記の文字列を変換する際に大きく挙動が変わる。
- 前からそれぞれint, floatと判断できるところまで切り取り変換する。
- 余談だが var_dump((int)"0xFF") は 0 である。
演算
- 可能な限り数値型にキャストしているような動作だが、intvalでもfloatvalでもないような挙動。
- 16進数はキャストする割に8進数や2進数はキャストしない。
その他数値にまつわるおかしい動作
比較がおかしい
<?php var_dump("255" == "0xFF"); // true var_dump( 255 == "0xFF"); // true var_dump("255" == "0xFFG"); // false var_dump( 255 == "0xFFG"); // true var_dump("10" == "1e1" ); // true var_dump("100" == "1e2" ); // true var_dump("100" == "1e2a"); // false var_dump( 100 == "1e2a"); // true var_dump( 1 == "1a" ); // true
intのキャストの意味不明な動作
<?php var_dump(intval( (0.1+0.7) * 10 )); // int(7) var_dump( (int)( (0.1+0.7) * 10 )); // int(7) var_dump( (int)( (0.2+0.6) * 10 )); // int(8)
マニュアルから抜粋だが上2つがおかしい。どういった原因でこういった挙動になるのかは暇があったら調べてみる。
補足情報
大部分の人にとってはどうでもいい実験コードと環境情報。
コード
<?php $values = array( 100, 1000, 10.01, 0, -100, -1000, "100", "1000", "10.01", "0", "-100", "-1000", "99999999999999999999999", "", " 100 ", "0b10", "0b12", "010", "019", "0xFF", "0xFFG", "1e2", "1e+2", "1e2e1", ); $functions = array( 'preg_match' => function($var){ return preg_match('/^-?\d+(\.\d+)?$/', $var); }, 'is_numeric' => function($var){ return is_numeric($var); }, 'ctype_digit' => function($var){ return ctype_digit($var); }, 'is_int' => function($var){ return is_int($var); }, 'is_float' => function($var){ return is_float($var); }, 'filter_var1' => function($var){ return filter_var($var, FILTER_VALIDATE_INT); }, 'filter_var2' => function($var){ return filter_var($var, FILTER_VALIDATE_FLOAT); }, 'intval' => function($var){ return intval($var); }, 'floatval' => function($var){ return floatval($var); }, 'plus' => function($var){ return 0 + $var; }, ); foreach($functions as $key => $func) { echo "-------------------------------\n"; echo $key . "\n\n"; foreach($values as $var) { echo str_pad(gettype($var) . ' ' . $var, 15, ' ', STR_PAD_LEFT); echo " : "; var_dump($func($var)); } }
環境
$ php -v
PHP 5.4.11 (cli) (built: Jan 16 2013 16:51:38)
Copyright (c) 1997-2013 The PHP Group
Zend Engine v2.4.0, Copyright (c) 1998-2013 Zend Technologies
with XCache v3.0.0, Copyright (c) 2005-2012, by mOo
with Xdebug v2.2.1, Copyright (c) 2002-2012, by Derick Rethans
with XCache Optimizer v3.0.0, Copyright (c) 2005-2012, by mOo
with XCache Cacher v3.0.0, Copyright (c) 2005-2012, by mOo
with XCache Coverager v3.0.0, Copyright (c) 2005-2012, by mOo
$ cat /proc/cpuinfo
processor : 0
vendor_id : GenuineIntel
cpu family : 6
model : 42
model name : Intel(R) Xeon(R) CPU E31240 @ 3.30GHz
stepping : 7
cpu MHz : 3292.521
cache size : 8192 KB
fpu : yes
fpu_exception : yes
cpuid level : 13
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss syscall nx rdtscp lm constant_tsc up arch_perfmon pebs bts xtopology tsc_reliable nonstop_tsc aperfmperf unfair_spinlock pni pclmulqdq ssse3 cx16 sse4_1 sse4_2 popcnt aes xsave avx hypervisor lahf_lm ida arat epb xsaveopt pln pts dts
bogomips : 6585.04
clflush size : 64
cache_alignment : 64
address sizes : 40 bits physical, 48 bits virtual
power management: