Javascript toFixed Not Rounding

Я использую JavaScript для привязки к некоторым флажкам, и toFixed(2) не округляет. Есть идеи, почему это не округление? Например, если номер 859.385 это только отображение 859.38 вместо 859.39,

Я также читал, что toFixed Можно ли по-разному округлять в зависимости от того, какой браузер вы используете, кто-нибудь знает способ обойти это так, чтобы мои вычисления javascript соответствовали моим вычислениям php?

var standardprice = parseFloat($('#hsprice_'+this.id.split('_')[1]).val());
var price =  parseFloat($('#hprice_'+this.id.split('_')[1]).val());
var discount =  parseFloat($('#hdiscount_'+this.id.split('_')[1]).val());
var deposit =  parseFloat($('#hdeposit_'+this.id.split('_')[1]).val());

var currSprice = parseFloat($('#hTotalSprice').val());
var currPrice = parseFloat($('#hTotalPrice').val());
var currDiscount = parseFloat($('#hTotalDiscount').val());
var currDeposit = parseFloat($('#hTotalDeposit').val());

currSprice += standardprice;
currPrice += price;
currDiscount += discount;
currDeposit += deposit;

$('#lblTotalSprice').text('$'+addCommas(currSprice.toFixed(2)));
$('#lblTotalPrice').text('$'+addCommas(currPrice.toFixed(2)));
$('#lblTotalDiscount').text('$'+addCommas(currDiscount.toFixed(2)));
$('#lblTotalDeposit').text('$'+addCommas(currDeposit.toFixed(2)));

$('#hTotalSprice').val(currSprice.toFixed(2));
$('#hTotalPrice').val(currPrice.toFixed(2));
$('#hTotalDiscount').val(currDiscount.toFixed(2));
$('#hTotalDeposit').val(currDeposit.toFixed(2));

26 ответов

Я еще не нашел номер, который toFixed10 делает неправильно. Кто-нибудь еще может?

Благодаря blg и его ответ, который указал мне на метод toFixed10() в Mozilla.

Используя это, я придумал этот короткий вкладыш, который действительно охватывает все случаи, упомянутые здесь...

function toFixed( num, precision ) {
    return (+(Math.round(+(num + 'e' + precision)) + 'e' + -precision)).toFixed(precision);
}

Я сделал это, чтобы использовать во всех финансовых данных как лучшую функцию округления. Вы можете проверить это на всех проблемных числах. Javascript допускает некоторую точность, поэтому я использовал его для округления почти каждого числа, как и ожидалось.

function roundTo(n, digits) {
        if (digits === undefined) {
            digits = 0;
        }

        var multiplicator = Math.pow(10, digits);
        n = parseFloat((n * multiplicator).toFixed(11));
        return Math.round(n) / multiplicator;
    }

В Chrome toFixed() Количество раундов:

859.385 ==> 859.38
859.386 ==> 859.39

Когда я смотрю на спецификации 5-го издания ECMAScript для.toFixed() (раздел 15.7.4.5), я не вижу в нем явного описания округления, хотя оно описывает довольно тупо, что может быть реализовано в Chrome.

Мне кажется, что если вы хотите управлять им с помощью явного округления, то вам, вероятно, следует использовать часто предлагаемый обходной путь:

var roundedNum = (Math.round( num * 100 ) / 100).toFixed(2);

Это гарантирует, что вы получите предсказуемое округление, к которому вы привыкли.

Рабочая демонстрация здесь: http://jsfiddle.net/jfriend00/kvpgE/

Так как в toFixed-функции javascripts, число с плавающей запятой5 не принадлежит к верхней половине целого числа, данное число округляется в меньшую сторону, если у вас есть такие числа:

859.385.toFixed(2) // results in 859.38

на самом деле вы можете добавить конечные числа с плавающей точкой (кроме нуля), как здесь:

859.3851.toFixed(2) // results in 859.39

Поэтому разработчики, как правило, добавляют цифры, такие как 0.00000000001 для того, чтобы оно было округлено соответствующим образом и чтобы случайно не изменить значение числа.

Поэтому я придумал функцию, которая добавляет такое число в зависимости от того, сколько цифр вы хотите, чтобы число с плавающей запятой фиксировалось:

    // both parameters can be string or number
    function toFixed(number, decimals) {
        var x = Math.pow(10, Number(decimals) + 1);
        return (Number(number) + (1 / x)).toFixed(decimals)
    }
    toFixed(859.385, 2) //results in 859.39

toFixedникогда не округлялся и не предназначался для этого.

Он почти не преобразует числа с плавающей запятой (экспонента, мантисса) в фиксированную точку (целое число, десятичные дроби) (см. Определение).

При этом может показаться, что это округление, потому что округленное число является наиболее близким приближением к исходному представлению с плавающей запятой.

К сожалению, широко распространено мнение, что toFixed можно использовать для округления... ну нет, нельзя, даже само название не имеет к этому никакого отношения.

Если вам нужно округление плюс toFixed обивка:

function round(num, precision) {
  var base = 10 ** precision;
  return (Math.round(num * base) / base).toFixed(precision);
}

Еще одно хорошее число, которое можно попробовать вместе с 35.855 - 1.005

Я не думаю, что решение Роберта Мессерле обрабатывает 1.005

Пример округления десятичной дроби здесь https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round$ revision / 1383484 # Decimal_rounding преобразует числа в экспоненциальную запись и, кажется, дает лучшие результаты.

Я создал скрипку здесь http://jsfiddle.net/cCX5y/2/ которая демонстрирует нативный пример Роберта Мессерла выше (он называется toFixedB) и один из документов Mozilla (называется toFixed10).

Я еще не нашел номер, который toFixed10 делает неправильно. Кто-нибудь еще может?

function roundup(num,dec){
    dec= dec || 0;
    var  s=String(num);
    if(num%1)s= s.replace(/5$/, '6');
    return Number((+s).toFixed(dec));
 }

 var n= 35.855
 roundup(n,2)

/* возвращаемое значение: (число) 35,86 */

toFixed() работает правильно! Проблема в том, что 859.385 не имеет представления как float на компьютере. Это сканируется как ближайшее возможное значение = 859,384999999999991. И округление этого значения до 2 цифр составляет 859,38, а не 859,39.

Это является причиной того, что многие (особенно старые для торговли, например, COBOL) языки программирования поддерживают числа BCD (двоично-десятичные десятичные числа), где каждая цифра кодируется сама по себе в 4 бита (как шестнадцатеричный код без использования AF).

Общее решение для цен: - Рассчитайте в центах / копейках и напечатайте NUMBER/100.

Примечание для других решений (функции, представленные здесь): они могут помочь для некоторых чисел, но в основном не работают, например, для 859.38499999.

Я столкнулся с этой же проблемой сегодня, и даже пробуя предложения в других ответах, я обнаружил, что все еще не получил ожидаемого результата. Наконец, так как я использую AngularJS для своего текущего проекта, столкнувшись с этим, я решил проверить, решал ли AngularJS ранее ту же самую проблему и действительно ли она была. Вот решение, которое они используют, и оно отлично работает для меня:

function toFixed(number, fractionSize) {
    return +(Math.round(+(number.toString() + 'e' + fractionSize)).toString() + 'e' + -fractionSize);
}

Найдено здесь: AngularJS filters.js источник

Если вы хотите получить число в качестве вывода, то рассмотрите Math.round() техника в других ответах.

Но если вы хотите получить строку в качестве вывода для представления человеку, то часто n.toLocaleString() является более полезным, чем n.toFixed(),

Зачем? Потому что это также добавит запятые или точки к голове большого числа, которые люди используют для чтения. Например:

var n = 1859.385

n.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2})

// Produces  1,859.39  in USA (locale en-US)
// Produces  1 859,39  in France (locale fr-FR)
// Produces  1.859,39  in Germany (locale de-DE)

В спецификации сказано, что при прохождении undefined в качестве первого аргумента будет использоваться собственная локаль пользователя (как указано в ОС). К сожалению, как показывает связанная документация, Mozilla использует локализацию en-US в этой ситуации, но в будущем она может соответствовать спецификации.

Вы можете использовать Math.round() округлить число. Если вы хотите округлить до определенного десятичного знака, вы можете использовать немного математики:

var result=Math.round(original*100)/100

Joy twindle answer здесь должен быть лучшим ответом вместо множества взломов.

   x = 1859.385;
   x = x.toFixed(2);
   alert(x);

дает неправильное округление, т.е. 1859,38 вместо 1859,39

 x = 1859.385;
 x = x.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2});
 alert(x);

дает правильное округление 1 859,39

Единственная проблема заключается в том, что возвращаемый результат представляет собой строку с разделителем-запятой тысяч и поэтому не может использоваться для вычислений. Используя регулярное выражение, которое я получил от переполнения стека, чтобы удалить запятую, конечный результат

 x = 1859.385;
 x = x.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2});
 x=x.replace(/\,/g,'');
 alert(x);

теперь он возвращает 1859,39 и может быть использован для расчетов. Значение перед регулярным выражением, т.е. 1859,39, можно использовать для отображения html, в то время как неформатированное значение 1859,39 можно использовать для расчетов.

Я знаю, что это старый вопрос, но почему бы не сделать что-то вроде этого:

let TruncatedValueInString = ThenNumberYouWantToTruncate.toFixed(decPlaces + 1).slice(0, -1);

Примечание. Это решение .toFixed() может понадобиться только для обработки очень больших чисел.

Другие опубликованные решения хорошо работают для стандартных номеров (ниже 1e21)

Если вы хотите обрабатывать очень большие числа и очень маленькие дроби с с правильным округлением, без потери точности, без использования библиотек и с автономной функцией, одним из возможных эффективных решений является использование чтобы избежать потери точности из-за внутреннего округления javascript.

Функция ниже Является ли это.

Просто передайте большое число в виде строки с необходимым количеством цифр округления. Числа будут округлены правильно.

Концепция - это обычный способ разбить число на целую и десятичную части, но использовать удерживать эти 2 части. После этого округляйте и обрабатывайте по мере необходимости как положительные, так и отрицательные числа без использования какого-либо javascript. или числовые функции, чтобы избежать потери точности.

В качестве бонуса для очень особых случаев, когда число находится в очень большой нотации "е", я добавил функцию, которую я разместил здесь: /questions/47232258/kak-izbezhat-nauchnogo-oboznacheniya-bolshih-chisel-v-javascript/57469164#57469164 чтобы сделать это как встроенный процесс. Его можно удалить, если вы хотите сохранить шорткод и не беспокоиться о таких числах. [в коде есть комментарии для его удаления].

Я включил различные тестовые примеры, чтобы проверить различные возможности (некоторые из них немного странные, но тем не менее встречаются).

Эту идею можно улучшить.

Надеюсь, это будет полезно.

Я наткнулся на это удивление, почему Number.toFixed вел себя странно. Я вижу, что нативная функция ненадежна, что вызывает сожаление. Просматривая ответы из любопытства, я вижу, что большинство * из них не ведут себя правильно с числом 35.855 как любезно прокомментировал TJ Crowder каждый.

Может быть, это ответит на ваш вопрос.

function toFixed(n,precision) {
    var match=RegExp("(\\d+\\.\\d{1,"+precision+"})(\\d)?").exec(n);
    if(match===null||match[2]===undefined) {
        return n.toFixed(precision);
        }
    if(match[2]>=5) {
        return (Number(match[1])+Math.pow(10,-precision)).toFixed(precision);
        }
    return match[1];
    }

Регулярное выражение разбивает ваш номер на массив строк, таких как в toFixed(35.855,2): ["35.855", "35.85", "5"], Если последний номер (после точности отсечки) >=5, добавлять Math.pow(10, -precision) на обрезанный номер. Это добавит .01 если вы отрежете в 2 десятичных знака, .002 на 3 и так далее.

Я не знаю, является ли это надежным, так как он выполняет десятичную математику на поплавках, что может быть непредсказуемо. Я могу сказать, это раунды 35.855 вплоть до 35.86,

Это происходит из-за представления с плавающей точкой в ​​JavaScript.

Попробуй это:

Number.prototype.round = function(digits) {
    digits = Math.floor(digits);
    if (isNaN(digits) || digits === 0) {
        return Math.round(this);
    }
    if (digits < 0 || digits > 16) {
        throw 'RangeError: Number.round() digits argument must be between 0 and 16';
    }
    var multiplicator = Math.pow(10, digits);
    return Math.round(this * multiplicator) / multiplicator;
}

Number.prototype.fixed = function(digits) {
    digits = Math.floor(digits);
    if (isNaN(digits) || digits === 0) {
        return Math.round(this).toString();
    }
    var parts = this.round(digits).toString().split('.');
    var fraction = parts.length === 1 ? '' : parts[1];
    if (digits > fraction.length) {
        fraction += new Array(digits - fraction.length + 1).join('0');
    }
    return parts[0] + '.' + fraction;
}

Использование:

var n = 859.385;
console.log(n.round(2)); // 859.39
console.log(n.fixed(2)); // 859.39
console.log(n.round(4)); // 859.385
console.log(n.fixed(4)); // 859.3850

The toFixed()Метод в JavaScript не соответствует стандартным правилам округления чисел, заканчивающихся на . Он использует стратегию «округления до половины вверх», что означает округление в большую сторону, когда следующая цифра равна 5 или больше.

Если вам нужно округлить числа, оканчивающиеся на.5более последовательным способом вы можете создать собственную функцию округления. Вот пример:

      function roundNumber(number, decimalPlaces) {
  const factor = 10 ** decimalPlaces;
  return Math.round(number * factor) / factor;
}

const number = 3.145;
const roundedNumber = roundNumber(number, 2);

console.log(roundedNumber); // Output: 3.15

Альтернативно для достижения этой цели вы можете использовать метод toLocaleString() . Вот еще один пример:

      const number = 3.145;
const locales = window.navigator.language;
const options = { maximumFractionDigits: 2 };
const roundedNumber = Number(number.toLocaleString(locales, options));

console.log(roundedNumber); // Output: 3.15

Это может помочь

    tofix2Decimals=function(float){
        if(parseInt(float)==float)return float.toFixed(2);
        $decimals=/\.(\d+)/.exec(float)[1].length;
        $decimals=$decimals>=2?$decimals+1:3;
        float+=Math.pow(10,-$decimals);
        return float.toFixed(2);
    }

Хотелось чего-то лаконичного и точного с хорошим сочетанием читабельности и скорости. После прочтения всех ответов на этот вопрос и этого особенно полезного ответа на аналогичный вопрос, это мое решение.

         const round = (numberToRound, digits = 0, toFixed = false) => {
  const precision = 10 ** digits;
  const n = numberToRound * precision * (1 + Number.EPSILON);
  const roundedNumber = Math.round(n) / precision;
  return toFixed ? roundedNumber.toFixed(digits) : roundedNumber;
};

// rounding by half
console.log(round(0.5));
console.log(round(-0.5));

// fixed decimals
console.log(round(0.5, 2), '( Type:',typeof round(0.5, 2), ')');
console.log(round(0.5, 2, true), '( Type:',typeof round(0.5, 2, true), ')');
console.log(round(-0.5, 2), '( Type:',typeof round(-0.5, 2), ')');
console.log(round(-0.5, 2, true), '( Type:',typeof round(-0.5, 2, true), ')');

// edge cases
console.log(round(1.005, 2) === 1.01);
console.log(round(-1.005, 2) === -1.01);
console.log(round(39.425, 2) === 39.43);
console.log(round(-39.425, 2) === -39.43);
console.log(round(1234.00000254495, 10) === 1234.000002545);
console.log(round(-1234.00000254495, 10) === -1234.0000025449);

// edge cases from other answer's comments.
console.log(round(859.385, 2));
console.log(round(859.3844, 2));
console.log(round(0.000000015, 8))
console.log(round(35.855, 2));

Я не люблю toFixed как логический параметр, но пока он работает.

У меня есть подходящее решение от Лам Вей Ли

function round(number, precision) {
  var shift = function (number, exponent) {
    var numArray = ("" + number).split("e");
    return +(numArray[0] + "e" + (numArray[1] ? (+numArray[1] + exponent) : exponent));
  };
  return shift(Math.round(shift(number, +precision)), -precision);
}

Результат испытаний

round(1.050, 1); // expected 1.1 , result 1.1  (correct)
round(1.005, 2); // expected 1.01, result 1.01 (correct)

round(3456.3456,  3); // 3456.346
round(3456.3456,  2); // 3456.35
round(3456.3456,  1); // 3456.3
round(3456.3456,  0); // 3456
round(3456.3456, -1); // 3460
round(3456.3456, -2); // 3500
round(3456.3456, -3); // 3000

round(undefined, 1        ); // NaN
round(null     , 1        ); // NaN
round("a"      , 1        ); // NaN
round(1        , null     ); // NaN
round(1        , undefined); // NaN
round(1        , "a"      ); // NaN
function toFixed(num, decimals) {
    return (Math.round((num + Number.EPSILON) * Math.pow(10, decimals)) / Math.pow(10, decimals)).toFixed(decimals)
}

Я нашел здесь правильный ответ -> Округлить не более чем до двух десятичных знаков (только при необходимости)

Math.round((num + Number.EPSILON) * 100) / 100

Вы должны добавить Number.EPSILON к округленному числу.

Я потратил на это слишком много времени.

      export function parseFloat<TDefault>(nbr: string | number, defaultValue: TDefault): number | TDefault
export function parseFloat(nbr: string | number): number | null
export function parseFloat(nbr: string | number, defaultValue: any = null): number | null {
    if(nbr == null || nbr === '') return defaultValue
    const val = globalThis.parseFloat(nbr as string)
    return Number.isFinite(val) ? val : defaultValue
}

export function fullWide(n: number|string):string {
    return parseFloat(n,0).toLocaleString('en-US', {useGrouping: false, maximumFractionDigits: 20})
}

export function round(value: string | number, decimalDigits = 0): number {
    return +(`${Math.round(+(`${fullWide(value)}e${decimalDigits}`))}e${-decimalDigits}`)
}

Проходит все эти тесты:

      test('round', () => {
    expect(round(1.2345, 0)).toBe(1)
    expect(round(1.2345, 1)).toBe(1.2)
    expect(round(1.2345, 2)).toBe(1.23)
    expect(round(1.2345, 3)).toBe(1.235)
    expect(round(1.2345, 4)).toBe(1.2345)
    expect(round(3.141592653589793, 15)).toBe(3.141592653589793)
    expect(round(3.141592653589793, 13)).toBe(3.1415926535898)
    expect(round(12.345, -1)).toBe(10)
    expect(round(4_500, -3)).toBe(5000)
    expect(round(89.684449, 2)).toBe(89.68)
    expect(round(89.685, 2)).toBe(89.69)
    expect(round(0.000000015, 8)).toBe(0.00000002)
    expect(round(1e-20, 20)).toBe(1e-20)
    expect(round(1.5e-19, 20)).toBe(0.00000000000000000015)
    expect(round(1.5e-19, 19)).toBe(0.0000000000000000002)
    expect(round('1.5e-19', 19)).toBe(0.0000000000000000002)
    expect(round('bacon', 19)).toBe(0)
})

У меня это сработало - "хакерское"

      function customTofix(value, precision) {
    let decimalVal = 0;
    
    if (value !== null) {
        let appendValue = (((value - Math.floor(value)) !== 0) ? (precision <= (value.toString().split(".")[1].length || 0)) ? '1' : '' : '');
        decimalVal = parseFloat(value.toString() + appendValue).toFixed(precision)
    }

    return decimalVal
}

Моя тренировка:

Для меня этого было достаточно, чтобы включить в общий javascript, например helpers.js

      // eslint-disable-next-line no-extend-native
Number.prototype.toFixed = function (fractionDigits) {
    var precisionTens = Math.pow(10, fractionDigits || 0);
    return (Math.round(Number(this) * precisionTens) / precisionTens);
}

Это перезапишет собственный javascripttoFixed()функция прототипа.

Вот мое решение, включающее 3 функции.

  1. Правильно округлить.
  2. Отображать только в количестве.(Предотвращение научной записи)
  3. Удаляет нули в конце.

Я объединил ответ @user2823670 и это вместе.

var roundUp = function(num, precision) {
    // Return '' if num is empty string
    if (typeof num === 'string' && !num) return '';

    // Remove exponential notation
    num = toPlainString(num);

    // Fixed round up
    var result = +((+(Math.round(+(num + 'e' + precision)) + 'e' + -precision)).toFixed(precision));

    // Remove exponential notation (once again)
    result = toPlainString(result);

    return result;
};

var toPlainString = function(num) {
    return ('' + num).replace(/(-?)(\d*)\.?(\d+)e([+-]\d+)/,
        function(a, b, c, d, e) {
            return e < 0
                ? b + '0.' + Array(1 - e - c.length).join(0) + c + d
                : b + c + d + Array(e - d.length + 1).join(0);
        }
    );
}

MDM имеет надежную реализацию округления. Это точнее, чем большинство, если не все ответы здесь.

Другие вопросы по тегам