Тестирование ошибок округления в JavaScript

Как я могу определить, является ли число слишком большим (вызовет ошибки округления при выполнении математики) в JavaScript.

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

function formatPercent(x, decimals) {
     var n = parseFloat(x);                 // Parse (string or number)
     if ($.isNumeric(n) === false) {
         return x;                          // Return original if not a number
     } else {
         return n.toFixed(decimals) + '%';  // Return formatted string
     }
 };

alert(formatPercent(276403573577891842, 2)); // returns 276403573577891840.00%

Поскольку форматирование такого огромного числа является краеугольным камнем и не ожидается, я бы предпочел просто вернуть число в том виде, в котором оно было передано. Каков предел до того, как начнутся ошибки округления, и как я могу их проверить?

Обновление:

Что такое Наибольшее целочисленное значение JavaScript, к которому число может перейти без потери точности? говорит, что точность работает до +/- 9007199254740992. Я тестирую, чтобы убедиться, что это все, что мне нужно проверить, чтобы безопасно ошибиться и вернуть переданное в значение без изменений.


person Justin    schedule 19.07.2013    source источник
comment
На самом деле это зависит от источника чисел и от того, что вы собираетесь с ними делать. Честно говоря, я не вижу приложения с такими большими процентами, так для чего это на самом деле?   -  person Patrick Roberts    schedule 19.07.2013
comment
Я пишу модульные тесты (QUnit) и хочу убедиться, что мои результаты надежны. У меня есть аналогичные функции, которые форматируют числа (с запятыми, десятичными знаками) для таких вещей, как показы и клики, которые будут большими числами ... все еще не ожидая 64-битного int, подобного приведенному выше, но при написании моих модульных тестов это будет в любом случае приятно поймать.   -  person Justin    schedule 20.07.2013
comment
Статья, на которую вы ссылаетесь, действительно имеет смысл (9007199254740992).toString(16) == 20000000000000 Так что я думаю, просто добавив оператор if, говорящий, что он выше или равен этому значению, определит, доверять ли ему его реальное значение.   -  person Patrick Roberts    schedule 20.07.2013


Ответы (4)


Если вы всегда передаете x в виде строки, это гарантирует отсутствие ошибок округления. Проблема в том, что 276403573577891842 округляется точно так же, как анализируется числовой литерал, но если вы используете строки, этого никогда не произойдет. Попробуйте сделать это:

function formatPercent(x, decimals) {
    if(typeof x != "string" && typeof x != "number") return x;
    x = x+"";//convert to string if it is a number
    var r = /^(?:(\d+)(\.\d*)?|(\d*)(\.\d+))$/;// RegExp for matching numerical strings
    return x.replace(r, function(match, int, dec){
        if(decimals>0){
            int = (typeof int == "string"?int:"");//if passed string didn't have integers
            dec = (typeof dec == "string"?dec:".");//if passed string didn't have decimals
            while(dec.length-1<decimals) dec += "0";//pad zeroes until dec.length-1==decimals
            return int+dec.slice(0,decimals+1)+"%";//in case dec.length-1>decimals
        }
        int = (typeof int == "string"?int:"0");//if passed string didn't have integers
        return int+"%";
    });
    // Return formatted string or original string conversion if no match found
}

alert(formatPercent("276403573577891842", 1));// returns 276403573577891842.0%
alert(formatPercent("276403573577891842.55", 1));// returns 276403573577891842.5%
alert(formatPercent("276403573577891842.55", 0));// returns 276403573577891842%
alert(formatPercent(".55", 1));//returns .5%
alert(formatPercent(".55", 0));//returns 0%
alert(formatPercent(276403573577891842, 1));// returns 276403573577891840.0%
alert(formatPercent("this is not a number", 2));// returns this is not a number
alert(formatPercent({key:"not number or string"}, 2));// returns the object as it was

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

person Patrick Roberts    schedule 19.07.2013
comment
Спасибо, +1, так как это решает математическую задачу. Однако я не думаю, что хотел бы использовать его в производстве, поскольку он довольно сложный, и, как вы упомянули в своем комментарии, обработка таких больших чисел не нужна. Однако необходимо знать, когда переданное значение слишком велико. Я обновлю свой вопрос. - person Justin; 20.07.2013

function formatPercent(x, decimals) {
     var n = parseFloat(x);                 // Parse (string or number)
     if ($.isNumeric(n) === false) {
         return x;                          // Return original if not a number
     } else {
         var d = Math.pow(10, decimals)
         return (Math.round(n * d) / d).toString() + "%";
      }
 };

Использование округления приведет к удалению десятичного числа, если оно равно 0,00.

person kcathode    schedule 19.07.2013
comment
Это неверно, так как выполнение кода дает 276403573577891840%, а не 276403573577891842% - person Patrick Roberts; 20.07.2013
comment
Ах, правда, max int составляет 2 ^ 53, как показано ниже. Однако работает для меньших поплавков. stackoverflow.com/questions/307179/ - person kcathode; 20.07.2013

Это то, что я использовал, чтобы поймать go-t">слишком большие или слишком маленькие целые числа:

function getSafeNumber(x) {
     var n = parseFloat(x);    // Parse and return a floating point number
     if ($.isNumeric(n) === false || 
         n >= 9007199254740992 || 
         n <= -9007199254740992) {
         return false;         // Not numeric or too big or too small
     } else {
         return n;             // return as number
     }
 };
person Justin    schedule 19.07.2013
comment
Возможно, вы захотите сделать >=... например: (9007199254740993 > 9007199254740992) === false - person Patrick Roberts; 20.07.2013
comment
Я не уверен, что понимаю. Если n = 9007199254740993, он вернет false как есть. - person Justin; 20.07.2013
comment
Введите var n = 9007199254740993;n; в консоль и посмотрите, что там написано. Он будет читать 9007199254740992. - person Patrick Roberts; 20.07.2013

Ошибки такого типа могут быть вызваны не более чем девятью квардмиллиардами из-за внутреннего представления

person HimanshuArora9419    schedule 11.07.2014