JSON::XSを使うと、小数は浮動小数点数にパースしてくれる。
use JSON::XS qw(decode_json); my ($double) = @{decode_json("[0.6]")};
この値、printすると "0.6" なのだけど、0.6とイコールではない。
print $double, "\n"; # 0.6 print +($double - 0.6), "\n"; # 1.11022302462516e-16
なので、直感と違う振る舞いをしたりする。
# 3 / 0.6 == 5 printf "%s / %s == %s\n", 3, 0.6, int(3 / 0.6); # 3 / 0.6 == 4 <- unexpected :( printf "%s / %s == %s\n", 3, $double, int(3 / $double);
仕方ないので端数を四捨五入して回避。
$double = int($double * 100 + .5) / 100; # 3 / 0.6 == 5 printf "%s / %s == %s\n", 3, $double, int(3 / $double);
こんな手もありそう。
$double = "$double" + 0; # 3 / 0.6 == 5 printf "%s / %s == %s\n", 3, $double, int(3 / $double);
余談
RTには@__kan さんが上げてるけど、仕様と言えば仕様な気もする。Dumpしてみたけど違いはわからず。
use Devel::Peek; Dump(0.6); # SV = NV(0x7fd549839808) at 0x7fd54982a488 # REFCNT = 1 # FLAGS = (NOK,READONLY,pNOK) # NV = 0.6 Dump($double); # SV = NV(0x7fd549839810) at 0x7fd54982a440 # REFCNT = 1 # FLAGS = (PADMY,NOK,pNOK) # NV = 0.6
(10/24 追記)環境とか
再現しない @maka2_donzoko さんとかいらっしゃるみたいなので一応環境を。以下の環境では現象が再現。
% perl -V Summary of my perl5 (revision 5 version 12 subversion 2) configuration: Platform: osname=darwin, osvers=11.4.0, archname=darwin-2level uname='darwin ... 11.4.0 darwin kernel version 11.4.0: mon apr 9 19:32:15 pdt 2012; root:xnu-1699.26.8~1release_x86_64 x86_64 ' config_args='-de -Dprefix=...' hint=recommended, useposix=true, d_sigaction=define useithreads=undef, usemultiplicity=undef useperlio=define, d_sfio=undef, uselargefiles=define, usesocks=undef use64bitint=define, use64bitall=define, uselongdouble=undef usemymalloc=n, bincompat5005=undef Compiler: cc='cc', ccflags ='-fno-common -DPERL_DARWIN -fno-strict-aliasing -pipe -fstack-protector -I/usr/local/include', optimize='-O3', cppflags='-fno-common -DPERL_DARWIN -fno-strict-aliasing -pipe -fstack-protector -I/usr/local/include' ccversion='', gccversion='4.2.1 Compatible Apple Clang 4.0 ((tags/Apple/clang-421.0.57))', gccosandvers='' intsize=4, longsize=8, ptrsize=8, doublesize=8, byteorder=12345678 d_longlong=define, longlongsize=8, d_longdbl=define, longdblsize=16 ivtype='long', ivsize=8, nvtype='double', nvsize=8, Off_t='off_t', lseeksize=8 alignbytes=8, prototype=define ...以下略...
- OS X 10.8.2, perl-5.12.2, JSON-XS-2.33
- OS X 10.7.4, perl-5.16.0, JSON-XS-2.33
- CentOS 5.8, perl-5.12.2, JSON-XS-2.3
- Ubuntu 9.10, perl-5.14.1, JSON-XS-2.32
後、configで-Duselongdoubleとかつかても変わるかもってIRCで流れてた。さらに @syohex さんが情報くれた。
i386 Linux環境でJSON::XSを makeするときCFLAGSに"-msse2 -mfpmath=sse"をつけると再現しました。80bitの拡張浮動小数点を使うと再現しないので精度の問題だと思います。
JSON::XSの独自実装の atofを Perl本体のものを利用するように変更したところ、手元では期待どおり動きました。自前の atofがいまいちだったというのが結論でしょうか. https://gist.github.com/3944470
(10/25 追記)改めて問題点を整理
@tokuhirom さんにはてブもらいましたが、問題点は以下に尽きますね。
printf("%.20f\n", $dat); printf("%.20f\n", 0.6);0.60000000000000008882 0.59999999999999997780
要は単に、処理系と独自実装のデコード結果が一致するか否かってのが問題でした。