読者です 読者をやめる 読者になる 読者になる

NaN を数値比較

perl

NaN という文字列を <=> で比較すると -1, 0, 1 ではなくて undef が返るというお話。(v5.8.8 built for i386-linux-thread-multi)

ことの発端は、DBD::CSV で ORDER BY するとエラー、という現象。

id,foo
1,FOO
2,BAR
3,NaN

例えばこんなのを foo で ORDER BY できない。

@> SELECT * FROM test.csv ORDER BY foo;
Argument "BAR" isn't numeric in numeric comparison (<=>) at /usr/lib/perl5/site_perl/5.8.8/SQL/Statement.pm line 972, <GEN1> line 4.
DBD::CSV::st execute failed: Sort subroutine didn't return a numeric value at /usr/lib/perl5/site_perl/5.8.8/SQL/Statement.pm line 985, <GEN1> line 4.
 [for Statement "SELECT * FROM test.csv ORDER BY foo
"].

どうやら NaN という文字列が <=> で特別扱いされるのが原因。
SQL::Statement の中で以下のような部分があって、

        my $sortFunc = sub {
# 略
                } elsif ( is_number($c,$d) ) {
                    $result = ($c <=> $d);
# 略
            $result;
        };

ここで $c または $d に "NaN" という文字列が入ってくると $result == undef になり、あとの sort で $sortFunc を使って

Sort subroutine didn't return a numeric value

とエラーになる。しかし 10年近く Perl さわってるのに、NaN が特別扱いされるのは知らなかった…… perlop と sort のリファレンス(perldoc -f sort)にはちゃんと書いてあります。

結局 SQL::Statementが、数値っぽかったら数値比較、文字列っぽかったら文字列比較、などと気を利かせてくれたのが裏目。
しかし sort 中に比較する 2値が数値っぽいか文字列っぽいか、で比較関数が切り替わるってのも微妙だ。文字列と数値(に見えるもの) が混じっているカラムで ORDER BY すると、数値としても文字列としてもまともに sort されていない、変な順序で返ってきてしまう可能性がある。

仕方ないので、ORDER BY せずに取得してから自前で cmp 使って sort し直して凌いだ。

ちなみに Mac OSX の "v5.8.6 built for darwin-thread-multi-2level" では NaN はサポートされていないらしく、<=> で比較しても undef ではなく 0 が返る。