銀の弾丸

プログラミングに関して、いろいろ書き残していければと思っております。

意外に悩ましい整数部分の四捨五入

f:id:takamints:20170718211752p:plain
photo credit: danmachold calculator via photopin (license)

JavaScriptで整数部分を四捨五入する場合の注意点。桁落ちに気を付けましょうってお話です。

JavaScriptで、小数を整数に四捨五入するにはMath.roundを使いますね。

でもこれ、残念なことに桁数指定ができません。

なので例えば、実数nを小数点以下2桁に丸めたい場合はMath.round(n * 100) / 100 などとしますよね。

毎回掛けたり割ったりするのも邪魔くさいので関数化すればヨロシイですねと、 実数 n を小数点以下 m 桁に四捨五入する関数は、以下のようになりますよっと(Nodeのモジュールとして書いています)。

桁数指定で小数点以下を四捨五入する関数(モジュール)

"use strict";
var assert = require("assert");

/**
 * 小数部の桁数指定で四捨五入。
 *
 * @param {number} n 元の数値
 * @param {number} m 桁数指定。0以上の整数。
 * @returns {number} 結果を返す
 */
function round(n, m) {
    assert(m == Math.round(m));
    assert(m >= 0);
    var r = Math.pow(10, m);
    return Math.round(n * r) / r;
}
module.exports = round;

ここで、 m に負の値を与えれば、整数部分も丸められるように一見思えるのですが、 m の絶対値が大きくなると桁落ちが発生して正しい結果が得られません。いつ桁落ちするかわからないので結局安心して使えません。

桁数指定で整数部分も四捨五入できる関数

てなことで、整数部も正しく四捨五入するために以下のようにしてみました。

"use strict";
var assert = require("assert");

/**
 * 桁数指定で四捨五入。
 *
 * @param {number} n 元の数値
 * @param {number} m 桁数指定。負の値で整数部を四捨五入
 * @returns {number} 結果を返す
 */
function round(n, m) {
    assert(m == Math.round(m));
    if(m < 0) {
        var i = Math.floor(n);
        var R = Math.pow(10, -m);
        var sgn = Math.sign(n);
        var h = sgn * R / 2;
        var mod = sgn * Math.abs(i % R);
        var up = sgn * (sgn >= 0 ? (mod >= h ? R : 0) : (mod < h ? R : 0));
        return  i - mod + up;
    }
    var r = Math.pow(10, m);
    return Math.round(n * r) / r;
}
module.exports = round;

当初「簡単!簡単!」と軽い気持ちで書いたら、 n が負の時に結果がおかしくて、最終的に結構ややこしくなってしまいました。 m < 0 の時、とか、 n の符号による判定が離散的でスッキリしないですねえ。なんかいい方法無いものかな。

言い訳

実は、あまり多くのテストケースを通していないので、ちょっと不安が残っております。

あと、npmには負の桁数指定ができるroundやceil, floorを提供するモジュールはあるようなのですが、 桁落ちに対してどうなっているのか明記されたものが、よくわかりませんでした(英語力の問題かも)。