明日になったら本気出せる

底辺Web系エンジニアの日記

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

  • 与えられた文字列 text のすべての文字が 数字であるかどうかを調べる。
  • 文字列でない場合はfalseになるはずだが、ASCII値の範囲での話。なので0~255までの数値リテラルの場合はfalseとなり、それを超えると内部的に文字列とみなしている。
  • phpソースコード上でわざわざ上のようなコード書いてる・・・ctypeというのを忠実に再現しようとした結果?

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
  1. 「===」「!===」演算子の場合は上記の型の変換を行わない。
  2. 片方のオペランドが数値リテラルだと無理やり数値型にする。演算と時と同じようなキャスト。
  3. オペランドが文字列かつ数値に変換できるときは変換してから比較する。
  4. そうでない場合は文字列として比較する。

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が好きです。

I LOVE PHP !!


補足情報

大部分の人にとってはどうでもいい実験コードと環境情報。

コード

<?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));
    }
}

環境

$ cat /etc/redhat-release
CentOS release 6.3 (Final)

$ 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: