web-dev-qa-db-ja.com

JavaScriptでのガウス/バンカーの丸め

私はC#でMath.Round(myNumber, MidpointRounding.ToEven)を使用してサーバー側の丸めを行ってきましたが、ユーザーはサーバー側の操作の結果がどうなるかを「ライブ」で知る必要があります( Ajax リクエスト)C#で使用されるMidpointRounding.ToEvenメソッドを複製するJavaScriptメソッドを作成します。

MidpointRounding.ToEvenは、Gaussian / banker's rounding であり、ここ で説明されている会計システムの非常に一般的な丸め方法です

誰かがこれについて何か経験がありますか?オンラインで例を見つけましたが、与えられた小数点以下の桁数に丸められません。

59
Jimbo
function evenRound(num, decimalPlaces) {
    var d = decimalPlaces || 0;
    var m = Math.pow(10, d);
    var n = +(d ? num * m : num).toFixed(8); // Avoid rounding errors
    var i = Math.floor(n), f = n - i;
    var e = 1e-8; // Allow for rounding errors in f
    var r = (f > 0.5 - e && f < 0.5 + e) ?
                ((i % 2 == 0) ? i : i + 1) : Math.round(n);
    return d ? r / m : r;
}

console.log( evenRound(1.5) ); // 2
console.log( evenRound(2.5) ); // 2
console.log( evenRound(1.535, 2) ); // 1.54
console.log( evenRound(1.525, 2) ); // 1.52

ライブデモ: http://jsfiddle.net/NbvBp/

これのより厳密な扱いのように見えるもの(私はそれを使用したことがない)については、これを試すことができます BigNumber 実装。

76
Tim Down

受け入れられた回答は、指定された数の場所に丸められます。その過程で、数値を文字列に変換するtoFixedを呼び出します。これは高価なので、以下の解決策を提供します。 0.5で終わる数値を最も近い偶数に丸めます。任意の桁数への丸めは処理しません。

function even_p(n){
  return (0===(n%2));
};

function bankers_round(x){
    var r = Math.round(x);
    return (((((x>0)?x:(-x))%1)===0.5)?((even_p(r))?r:(r-1)):r);
};
6
soegaard

これは@soegaardの優れたソリューションです。小数点で機能するようにする小さな変更を次に示します。

bankers_round(n:number, d:number=0) {
    var x = n * Math.pow(10, d);
    var r = Math.round(x);
    var br = (((((x>0)?x:(-x))%1)===0.5)?(((0===(r%2)))?r:(r-1)):r);
    return br / Math.pow(10, d);
}

そしてそれをしている間-ここにいくつかのテストがあります:

console.log(" 1.5 -> 2 : ", bankers_round(1.5) );
console.log(" 2.5 -> 2 : ", bankers_round(2.5) );
console.log(" 1.535 -> 1.54 : ", bankers_round(1.535, 2) );
console.log(" 1.525 -> 1.52 : ", bankers_round(1.525, 2) );

console.log(" 0.5 -> 0 : ", bankers_round(0.5) );
console.log(" 1.5 -> 2 : ", bankers_round(1.5) );
console.log(" 0.4 -> 0 : ", bankers_round(0.4) );
console.log(" 0.6 -> 1 : ", bankers_round(0.6) );
console.log(" 1.4 -> 1 : ", bankers_round(1.4) );
console.log(" 1.6 -> 2 : ", bankers_round(1.6) );

console.log(" 23.5 -> 24 : ", bankers_round(23.5) );
console.log(" 24.5 -> 24 : ", bankers_round(24.5) );
console.log(" -23.5 -> -24 : ", bankers_round(-23.5) );
console.log(" -24.5 -> -24 : ", bankers_round(-24.5) );
5
xims

これは、ボトムアンサーが受け入れられたものよりも優れている珍しいスタックオーバーフローです。 @ximsソリューションをクリーンアップし、もう少し読みやすくしました。

function bankersRound(n, d=2) {
    var x = n * Math.pow(10, d);
    var r = Math.round(x);
    var br = Math.abs(x) % 1 === 0.5 ? (r % 2 === 0 ? r : r-1) : r;
    return br / Math.pow(10, d);
}
2
Adi Fairbank
const isEven = (value: number) => value % 2 === 0;
const isHalf = (value: number) => {
    const epsilon = 1e-8;
    const remainder = Math.abs(value) % 1;

    return remainder > .5 - epsilon && remainder < .5 + epsilon;
};

const roundHalfToEvenShifted = (value: number, factor: number) => {
    const shifted = value * factor;
    const rounded = Math.round(shifted);
    const modifier = value < 0 ? -1 : 1;

    return !isEven(rounded) && isHalf(shifted) ? rounded - modifier : rounded;
};

const roundHalfToEven = (digits: number, unshift: boolean) => {
    const factor = 10 ** digits;

    return unshift
        ? (value: number) => roundHalfToEvenShifted(value, factor) / factor
        : (value: number) => roundHalfToEvenShifted(value, factor);
};

const roundDollarsToCents = roundHalfToEven(2, false);
const roundCurrency = roundHalfToEven(2, true);
  • ToFixed()を呼び出すオーバーヘッドが気に入らない場合
  • 任意のスケールを供給できるようにしたい
  • 浮動小数点エラーを導入したくない
  • 読みやすく、再利用可能なコードが欲しい

roundHalfToEvenは、固定スケールの丸め関数を生成する関数です。 FPEの導入を避けるために、私はドルではなくセントで通貨操作を行っています。 unshiftパラメータは、これらの操作のシフト解除と再シフトのオーバーヘッドを回避するために存在します。

2
shiznit013

厳密に言えば、これらの実装はすべて、丸める桁数が負の場合を処理する必要があります。

これはエッジの場合ですが、それでも許可しないことをお勧めします(または、それが何を意味するかについて非常に明確にします。たとえば、-2は最も近い数百に丸められます)。

0