Perlの数値処理

Data::Util::is_number()を実装すべく数値処理を調べていたらPerlの暗黒面を覗いてしまった。

Perlの数値処理ルーチンは,Perl Parserが行うリテラルの処理と,Perl_grok_*を中心とした数値変換ルーチンが行う処理がある。この記事では便宜的に,前者で処理できることをparsability*1,後者で処理できることをgrokability*2と呼ぶ。
ある値が数値か否かを調べるにあたって問題になるのは,parsabilityとgrokabilityの処理範囲が異なるという点である。
parsabilityはeval()で,grokabilityはScalar::Util::looks_like_number()で調べることができる。

#!perl -w
use strict;
use Data::Util qw(neat);
use Scalar::Util qw(looks_like_number);
sub is_parsable{
        my($code) = @_;
        eval sprintf q{
                use strict;
                no warnings;
                return;
                my $dummy = (%s);
        }, $code;
        return !$@;
}

foreach my $x(
    42, 3.14, "42", "1_234",
    "NaN", "+Inf", "-Inf", "0 but true"){
        printf qq{%15s: parsability=%7s, grokability=%7s\n}, neat($x),
                is_parsable($x) ? "ok" : "not ok",
                looks_like_number($x) ? "ok" : "not ok";
}
__END__

実行結果:

             42: parsability=     ok, grokability=     ok
           3.14: parsability=     ok, grokability=     ok
           "42": parsability=     ok, grokability=     ok
        "1_234": parsability=     ok, grokability= not ok
          "NaN": parsability= not ok, grokability=     ok
         "+Inf": parsability= not ok, grokability=     ok
         "-Inf": parsability=     ok, grokability=     ok
   "0 but true": parsability= not ok, grokability=     ok

42や3.14のような値が数値なのは明らかであり,"42"のような値もPerlでは数値として扱われる。しかし,アンダースコアつきの文字列("1_234")はリテラルとしては問題ないが数値変換ルーチンは理解できず,"NaN", "Inf", "0 but true"は数値変換ルーチンは理解できるがリテラルとしてプログラム中に置くことはできない*3。looks_like_number("NaN")が真を返すのはいくらなんでもバグのような気がするが,とりあえず現状*4はこのようになっている。
そういうわけで,Data::Util 0.41のis_number()ではparsableかつgrokableな値のなかで,-Infを除く値を数値とみなすようになっている。これは無警告で数値に変換でき,またそのままリテラルとしてプログラムに埋め込める値なので,ほとんどの用途では望ましい結果を得られるはずだ。

(2008/12/09 追記)
ActivePerlだと"Inf"や"NaN"が数値化されたnanやinfが存在しないようだ。したがって,ActivePerlにおいては,"Inf"や"NaN"はlooks_like_number()が真を返し,数値コンテキストで警告は出されないが,数値と解釈すると0になるという奇妙なことになっている。かといってActivePerlでも9**9**9が"1.#INFという値になるので,無限値が存在しないわけでもないらしい。

*1:parsableな値はPerlプログラム中にリテラルとして置くことができる。

*2:grokableな値は-wの元でも数値コンテキストで警告を出さずに数値に変換される。

*3:ちなみに"Infinity"も許される。また,"NaN"や"Inf"はcase-insensitiveだが,"0 but true"は空白も含めたこの文字列だけが許される。なお,"-Inf"が例外的にリテラルとして扱えるのは,マイナスの後の識別子は文字列として扱われるという特例があるからである。

*4:Perl 5.8.0以降