「前パン愛好会」にようこそ! 

 電車といえば、やっぱり前パンがかっこいい!
 そんな皆さんに、前パンを揃えてみました。
 えっ?前パンって何??という方。
 先頭車の前側にパンタグラフがある電車の事です。
 ぜひ、前パンの魅力にはまって下さいませ♪

1.15 × 100 ≠ 115:プログラムで小数点のある数字を扱う時はトラップに注意

プログラムで小数を扱った場合、思ってもいない結果になることがあります。
自分も長年悩まされていましたが、原因が見えてきました。
Perlを例にして、対処法を含めて、備忘録として残しておきます。

整数を整えたいだけなのに、数が合わない!

ある処理をしていた時、整数の0埋めをしていたところ、普通は当たり前のようにズレはないのですが、
特定の場合に数字がずれてしまっていました。

$tmp = 115;
sprintf( “%04d", $tmp) → 0114

とはいえ、直接同じ"115″を入れてみたところ、当然のことながら正しく「0115」になりました。
変数に別の数字(114)を入れてみても、そのまま「0114」となるため、
なかなか原因が見出せませんでした。

小数を100倍にした場合に起こる事がわかる

ただ、色々試してみたところ、ひとつ気が付いたことがありました。
それは、小数を100倍にした場合に、起こる事がわかったんです。

$tmp = 1.15;
$tmp1 = $tmp * 100;
sprintf( “%04d", $tmp1)"; → 0114

試しに"11.5″を10倍して代入してみたところ、正しく「0115」になりました。
これまで10倍する処理は多用していたのですが、この通り特に問題はありませんでした。
今回のような100倍にする事がほとんどなかったので、このトラップに気が付かなかったんです。
小数点以下の位の多い小数を整数にして計算した場合と、そのまま整数で代入した場合で結果が異なる事がわかりました。

つまり、

1.15 × 100 ≠ 115

だったんです。
どうやら、小数はコンピューターにとって、扱いが難しいようです。

小数は二進数にすると近似値に

サーバーやPCなどコンピューターは、データを二進数で扱っています。
整数だと、数字を2で割れば、商と余りで二進数にすることができます。

ところが、小数の場合は、値が0で定まらない場合があるようです。
それを無限小数というようです。
こちらを参照してみて下さい。

無限小数が現れると、
コンピューターの内部ではある程度のところで調整して、近似値として処理するようです。

小数点以下20桁まで表示してわかった

自分のサーバーで、perlを用いてどうなるのか試してみました。
sprintf( “%f", $tmp)で桁数を指定しないで小数を表示すると、小数点以下8桁までの表示なんですね。
そうしますと、
 sprintf( “%f", 1.15 * 100 ) =115.00000000
と0が8個並ぶだけで何も違いがなく、原因がわかりませんでした。

そこで、小数点以下20桁まで表示してみました。
すると、大きな違いがでてきました。

$tmp sprintf( “%.20f", $tmp)
1.10 * 100 (110)110.00000000000001421085   ≧ 110
1.11 * 100 (111)111.00000000000001421085   ≧ 111
1.12 * 100 (112)112.00000000000001421085   ≧ 112
1.13 * 100 (113)112.99999999999998578915  < 113
1.14 * 100 (114)114.00000000000001421085   ≧ 114
1.15 * 100 (115)114.99999999999998578915  < 115
1.16 * 100 (116)115.99999999999998578915  < 116
1.17 * 100 (117)117.00000000000000000000  ≧ 117
1.18 * 100 (118)118.00000000000000000000  ≧ 118
1.19 * 100 (119)119.00000000000000000000  ≧ 119

なんと、整数よりも数字が小さくなりました。
これを「丸め誤差」といいます。
見ているものと、コンピューター内部で処理するものが違っていたんです。
これが大きな影響を生むことになります。

“%d"と"%f"の違い・「切り捨て」と「四捨五入」

整数の 0埋め桁合わせとして“%d"を活用していましたが、
単に「整数の文字列を返す」という意味ではなかったようです。
小数点以下を切り捨て、整数の文字列を返す」
ということのようでした。
つまり、「int」と同じ事をしていたようです。
さきほど小数点以下20桁まで表示したみましたので、その意味が見えてきました。
整数部分だけ取り出すと、 1.15 * 100 は 114 になりますって。
これまでintで上手くいかない事がありましたが、今なら理由がわかります。

そこで、切り捨てをしないで整数で返す方法を探していたら、
“%f"を活用すればいいという事がわかりました。
%dが使えない小数点の位を整えるために使っていましたが、実は全く違うものだったんです。
それは、
小数点以下第何位以下を四捨五入して、その第何位の文字列を返す」
ということのようでした。
そこで、桁数を”0”にすれば、小数点以下0桁で表示、
つまり整数で表示する事ができるんです。

両方の処理を比較してみました。

$tmp sprintf( “%04d", $tmp)sprintf( “%04.0f", $tmp)
1.10 * 100(110) 01100110
1.11 * 100 (111) 01110111
1.12 * 100 (112) 01120112
1.13 * 100 (113) 01120113
1.14 * 100 (114) 01140114
1.15 * 100 (115) 01140115
1.16 * 100 (116) 01150116
1.17 * 100 (117) 01170117
1.18 * 100 (118) 01180118
1.19 * 100 (119) 01190119

表を見てわかるように、
「 “%04.0f" を使う」事で、ようやく納得のいく結果が得られました。

小数を取り扱う時は、見た目のトラップに注意

データ処理のエラーから、小数に原因があることがわかり、その対処法をまとめてみました。
コンピューターは小数が苦手であり、見た目と扱いが違う事がわかりました。
違いを知っておくこと、大切ですね。

unix

Posted by 管理者