JavaScriptの小数点を含む計算での誤差

form1: x * y = z
× =

単純な掛け算を行うフォームを用意します。

このフォームは、xとyに数値を入力するとzに乗算結果を出力します。

スクリプトも単純で、テキストボックスの値を読み取って掛けるのみです。

 

ところが、x = 0.7, y = 1,400x = 10.2, y = 800 といった値を入力するとおかしな値が出力されます。

これは、JavaScriptの小数点計算を行う際に用いられる「IEEE 754」という規格で発生する誤差だそうです。

IEEE 754 - Wikipedia

浮動小数点数については以下のようなサイトに解説が掲載されています。

日経PC21 / 演算誤差の正体 - IEEE 754 浮動小数点数の仕組み

1週間で学ぶIT基礎の基礎 - 【5分で覚えるIT基礎の基礎】ゼロから学ぶ2進数 第4回:ITpro

ざっくり言うと、百万(1,000,000)を表現するのに通常だと7桁分の領域が必要だが、浮動小数点数で表現すると1E+7の”1"と"7"の部分の2桁の領域で済むよ、というお話です。

Q.E.D.の23巻では、『よく大きな数を「天文学的数」と言うが、宇宙の全ての原子の数でさえ80桁ほどしかない』と記載しています。

天文学的数を表現するのにも、浮動小数点数なら80桁分を必要としません。

 

さて、小数点を含む乗算を行うと、浮動小数点数計算の影響で不思議な現象が起こるというのはフォーム1で体験しました。

JavaScriptにはMathという関数が用意されています。

  • Math.ceil(n):切り上げ
  • Math.floor(n):切捨て
  • Math.round(n):四捨五入

通常の計算ではround、税額計算など切捨ての場合にはfloorを使うことになります。

form2: Math.floor(x * y) = z
× =

この場合でも、切捨ての場合は期待しない答えが出力されることになります。

レートxx.yyで両替したら1円足りないんですけどー、という具合です。

フォーム1で x * y を計算している時点でおかしくなってしまっているので、Math関数で小数点を扱う際にも影響が出てしまいます。

 

これはどうすればよいだろうか、という問題はずいぶん前からなされているようで。

イデアの1つに、小数点を取っ払えばよいのでは、というものがありました。

JavaScriptによる小数計算の誤差を無くす : アシアルブログ

10.2 * 800102 * 800 にして、最後に桁を戻せばよいんじゃないか、ということです。

注意点として、あくまで文字列操作で補うのであって、10倍するわけではありません。10倍してしまうと、浮動小数点数計算の誤差に巻き込まれてしまいます。

form3: Math.floor(x * y) = z (文字列操作)
× =

乗算前にxとyの小数点の位置を確認しておき、乗算後に末尾を削ります。

やっていることは10倍して10分の1にするだけですが、あくまで文字列操作による結果です。

とりあえず、誤差は消えた気がします。