javascriptでの小数点演算に泣かされているのでここで一旦整理したい。

端数処理の挙動

javascriptで端数処理を行う場合、以下のような選択肢がある。

<td>
  n以上の最近接整数値を返却する
</td>
<td>
  nを四捨五入する
</td>
<td>
  nを固定小数点表記にする
</td>
<td>
  nをパースして整数を返却する
</td>
<td>
  nと0を論理和する(整数化する)
</td>
Math.floor(n) n以下の最近接整数値を返却する
Math.ceil(n)
Math.round(n)
n.Fixed()
parseInt(n)
n|0

これらの出力を整理すると、

var a = [ 1.0, 0.9, 0.6, 0.5, 0.4, 0.1, 0,
		 -0, -0.1, -0.4, -0.5, -0.6, -0.9, -1.0 ];
for(var i = 0; i < a.length; i++){
	var n = a[i];
	console.log(n,
		Math.floor(n),
		Math.ceil(n),
		Math.round(n),
		n.toFixed(),
		parseInt(n),
		n|0);
}
n Math.floor(n) Math.ceil(n) Math.round(n) n.toFixed() parseInt(n) n|0
1 1 1 1 "1" 1 1
0.9 1 1 "1"
0.6 1 1 "1"
0.5 1 1 "1"
0.4 1 "0"
0.1 1 "0"
"0"
-0 -0 -0 -0 "0"
-0.1 -1 -0 -0 "-0"
-0.4 -1 -0 -0 "-0"
-0.5 -1 -0 -0 "-1"
-0.6 -1 -0 -1 "-1"
-0.9 -1 -0 -1 "-1"
-1 -1 -1 -1 "-1" -1 -1

-0.5の時のMath.round()や、-0をtoFixed()したときに符号が付かない点などが特徴的かな?

小数点指定桁での端数処理について

たとえば、 3.14159265359 という数値に対して、小数点第二位に四捨五入した結果の数値を得たい場合。
Math.round(3.14159265359*100)/100; とか、 (3.14159265359).toFixed(2)-0; という方法がある。
関数化するなら、Math.round(n*Math.pow(10,d))/Math.pow(10,d);とかになる。(※数値n、四捨五入する桁数dとした場合)

同様にして、小数点第二位で切り捨てしたい場合。
Math.floor(n\*Math.pow(10\*d))/Math.pow(10*d);…とすると問題が発生する場合がある。
たとえば n = 0.7 / 10 の場合。 小数点第二位で切り捨てるなら 0.07 が期待値だが実際には 0.06 になる。
なぜなら、 0.7 / 10 はjavascriptでは 0.07 ではなく、 0.06999999999999999 となるからだ。

また、1.14*10000 はjavascriptでは 11399.999999999998 となる。
これはIEEE754の仕様として許容されているもの、らしい。ぐぬぬ。

けども、実際問題困っちゃうので、回避策としては以下の方法を採用することにした。
「小数点第三位をtoFixedしてその値を小数点第二位で切り捨てる。」
関数表現なら、Math.floor(n.toFixed(d+1)\*Math.pow(10,d))/Math.pow(10,d);かな。

小数点同士の演算について

結構ずれる。
なので、有効桁を決めている場合は整数に変換して計算し、最後に戻すのが良い。

たとえば、 10.625001*3.01 の場合。

普通に計算すると31.98125301だが、javascriptでは31.981253009999996となってしまう。
10625001*301を行ったうえで、末尾8桁(小数6桁*小数2桁=小数8桁)を小数点数とすれば誤差は出ない。
つまり(10625001\*301 + "").replace(/^([0-9]\*)([0-9]{8})$/,"$1.$2");こう。

メソッドにするなら…

function multi(a, b){
    var ra = (a+"").split("."), rb = (b+"").split(".");
    if(ra.length == 1 && rb.length == 1){
        return a * b;
    }
    var da = Math.pow(10, (1 < ra.length) ? ra[1].length : 0);
    var db = Math.pow(10, (1 < rb.length) ? rb[1].length : 0);

    return (ra.join("")-0) * (rb.join("")-0) / da / db;
}

こんな感じ?
(あんまりテストしてないので使用する場合は自己責任でお願いします)

参考: