JavaScriptの動かないコード (中級編) かけ算を間違える
以下のJavaScriptコードが意図した動作をしないのは,なぜですか。(制限時間1分)
やりたい事:買い物の合計金額の計算
- 合計額=(商品単価+手数料単価)×商品個数
- 商品単価や手数料単価は0.1などの値も入る。
- 支払い金額は,端数切り捨てとする。
入力してください ・商品単価 <input type="text" id="box1"><br> ・手数料単価 <input type="text" id="box2" value="0.1" disabled="true"> (固定) <br> ・個数 <input type="text" id="box3"><br> <input type="button" value="合計額を算出" onClick=f()> <script language="JavaScript"> function f() { // 合計額=(商品単価+手数料単価)×商品個数 var unit_price = parseFloat( box1.value ); var unit_fee = parseFloat( box2.value ); var num = parseFloat( box3.value ); var total = (unit_price + unit_fee) * num; //alert( total ); // 支払額(日本円)に直す際には,端数切り上げ var total_yen = Math.floor( total ); alert( "合計額は" + total_yen + "円" ); } </script>
答え
これだと,
- 商品単価に 0.7
- 個数に 10
を入力した際,計算結果が「8」ではなく,「7」になる。
Math.floor( ( 0.1 + 0.7 ) * 10 ) = なぜか7
単価 0.7 円の商品を,単品手数料 0.1 円で 10 個購入すると, 8 円ではなく 7 円になってしまう。
0.8×10=8 のはずなのだが。
支払い側は安くなって大助かりであるが,売り手側はたまったものではない。
それ以上に,こういうエラーが起こると,売り手のためにその金額計算システムを開発したプログラマはたまったものではない。
動作は下記のコードで確認できる。
// 7 と表示
alert( Math.floor( ( 0.1 + 0.7 ) * 10 ) );
JavaScriptが,足し算や掛け算を間違えているように見える。
この原因は,コンピュータの内部で,浮動小数点数の計算が二進数によって行なわれているという点にある。
有限桁の十進数を,有限桁の二進数で表現できる保証はない。
したがって,数値が二進数に変換された時点で,数値計算で言うところの丸め誤差が生じる。
丸め誤差とは,桁数を省いたことによって生じる誤差である。
0.1も0.7も,計算時には実際の値とわずかに異なる値の二進数が用いられる。
上のコードで言うと,変数totalの値をalertで確かめると
7.999999999999999
が表示される。この値はほとんど 8 であるが,整数部を取得すると, 7 になってしまうのである。
この問題をJavaScriptで確かめるには,下記のサイトが顕著な例を載せていてわかりやすい。
0.01を100回足したのと,0.01に100をかけたのでは,結果が異なる。
このため,例えばfor文やwhile文などのループにおいて繰り返しの回数をカウントする際に,カウンターに小数を用いてしまうと,正常にループ終了判定ができない。(カウンター変数の値には整数を使うべき。)
- javascriptで0.1を100回足すといくつになるか
JavaScript以外のプログラミング言語でも同じ現象が起きる。
- PHPで浮動小数点数をきちんと計算(任意精度数学関数を使って解決する) http://pocket9.net/blog/?m=pc&a=postComment.view&post_id=18
回避策としては,問題の起こりそうな場所において,適当な精度の位で四捨五入を行なえばよい。
Math.floorによって小数を整数に変換する前に
// 第5位を残す
total = total.toFixed(5);
のようにすれば,totalの値は 8.00000 となる。
この上でMath.floorにかければ,正常に値 8 が出力される。
同じようなトラブル
似たような例として, 67.0 - 66.9 は,0.1にならない。
もっと小さい値になる。
なので,
Math.floor( ( 67.0 - 66.9 ) * 10 ) は,1ではなく0になる。
JavaScriptによる小数計算の誤差を無くす : アシアルブログ
http://blog.asial.co.jp/1191
- これはJavaScriptがIEEE 754という規格に従って実装されているためです。 つまり、この計算結果はJavaScriptの仕様なのでJavaScript的には正しい値であり、避けようがありません。