Гауссово / банковское округление в JavaScript - PullRequest
53 голосов
/ 24 июня 2010

Я использовал Math.Round(myNumber, MidpointRounding.ToEven) в C # для выполнения округления на стороне сервера, однако пользователь должен знать «вживую», каков будет результат операции на стороне сервера, что означает (избегая Ajax запрос) создание метода JavaScript для репликации метода MidpointRounding.ToEven, используемого C #.

MidpointRounding.ToEven - округление по Гауссу / банкира , очень распространенный метод округления для описанных систем учета здесь .

У кого-нибудь есть опыт с этим?Я нашел примеры в Интернете, но они не округляются до данного количества десятичных знаков ...

Ответы [ 6 ]

75 голосов
/ 24 июня 2010
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 .

6 голосов
/ 28 августа 2015

Принятый ответ округляется до заданного количества мест.В процессе он вызывает 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);
};
5 голосов
/ 12 октября 2016

Это отличное решение от @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) );
2 голосов
/ 29 июля 2018
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 голосов
/ 03 марта 2018

Это необычный стекопоток, где нижние ответы лучше, чем принятые.Просто убрал решение @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);
}
0 голосов
/ 03 апреля 2018

Строго говоря, все эти реализации должны обрабатывать случай отрицательного числа цифр для округления до.

Это крайний случай, но все же было бы разумно запретить его (или очень четко понимать, что это значит, например, -2 округляется до ближайшего количества в сотни).

...