2013-07-03 3 views
1

Это функция округления, которую мы используем (которая берется из ответов stackoverflow о том, как округлить). Он округляет половину до 2dp (по умолчанию)Ошибка округления Javascript

например. 2,185 должен пойти в 2,19

function myRound(num, places) { 
    if (places== undefined) { 
     // default to 2dp 
     return Math.round(num* 100)/100; 
    } 
    var mult = Math.pow(10,places); 

    return Math.round(num* mult)/mult; 
} 

Он работал хорошо, но теперь мы обнаружили некоторые ошибки в нем (как в хроме и работает как JScript классический ASP на IIS 7.5).

т.д .:

alert(myRound(2.185));  // = 2.19 
alert (myRound(122.185)); // = 122.19 
alert (myRound(511.185)); // = 511.19 
alert (myRound(522.185)); // = 522.18 FAIL!!!! 
alert (myRound(625.185)); // = 625.18 FAIL!!!! 

Кто-нибудь знает:

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

обновление: ОК, суть проблемы заключается в том, что в JS, 625,185 * 100 = +62518,499999 Как мы можем получить по этому поводу?

+0

Вы можете использовать это: 'return + (num.toFixed (2));'. Но в целом лучше всего выполнить все вычисления сначала и округлить результат непосредственно перед печатью. – Teemu

+0

@teemu, это округление, которое вызывает ошибку, и toFixed имеет такую ​​же ошибку, к сожалению. –

+0

Я тестировал это с помощью IE10 и FF21 с различными номерами, ошибок округления не было ... – Teemu

ответ

0

ОК, найдено «полное» решение проблемы.

Во-первых, donwnloaded Big.js здесь: https://github.com/MikeMcl/big.js/

Затем модифицированный источник, так что будет работать с JScript/осины:

/* big.js v2.1.0 https://github.com/MikeMcl/big.js/LICENCE */ 
var Big = (function (global) { 
    'use strict'; 
: 
// EXPORT 
return Big; 
})(this); 

Затем сделал мой расчет, используя большие типы и использовали Big toFixed (дп), а затем преобразуется обратно в ряд таким образом:

var bigMult = new Big (multiplier); 
var bigLineStake = new Big(lineStake); 
var bigWin = bigLineStake.times(bigMult); 
var strWin = bigWin.toFixed(2); // this does the rounding correctly. 
var win = parseFloat(strWin); // back to a number! 

Это в основном использует собственный Bigs округление в toFixed, который, кажется, правильно работать во всех случаев.

У Shame Big не существует способа конвертировать обратно в число без необходимости проходить через строку.

1

Попробуйте использовать функцию toFixed по значению. пример ниже:

var value = parseFloat(2.185); 
var fixed = value.toFixed(2); 
alert(fixed); 

Я попробовал и он работал хорошо.

EDIT: Вы всегда можете преобразовать строку в число, используя parseFloat (stringVar).

EDIT2:

function myRound(num, places) { 
    return parseFloat(num.toFixed(places)); 
} 

EDIT 3:

Обновленный ответ, испытано и работа:

function myRound(num, places) { 
    if (places== undefined) { 
    places = 2; 
    } 
    var mult = Math.pow(10,places + 1); 
    var mult2 = Math.pow(10,places); 
    return Math.round(num* mult/10)/mult2; 
} 

EDIT 4:

Испытано на большинстве примеров, отмеченных в комментариях:

function myRound(num, places) { 
    if (places== undefined) { 
    places = 2; 
    } 
    var mult = Math.pow(10,places); 
    var val = num* mult; 
    var intVal = parseInt(val); 
    var floatVal = parseFloat(val); 

    if (intVal < floatVal) { 
     val += 0.1; 
    } 
    return Math.round(val)/mult; 
} 

РЕДАКТИРОВАНИЕ 5: только решение, которое мне удалось найти, чтобы использовать строки, чтобы обойти на точной десятичной. Решение вставлено ниже, с использованием метода расширения прототипа String, replaceAt. Пожалуйста, проверьте и дайте мне знать, если кто-нибудь найдет пример, который не работает.

function myRound2(num, places) { 
    var retVal = null; 
    if (places == undefined) { 
     places = 2; 
    } 

    var splits = num.split('.'); 
    if (splits && splits.length <= 2) { 
     var wholePart = splits[0]; 
     var decimalPart = null; 
     if (splits.length > 1) { 
      decimalPart = splits[1]; 
     } 
     if (decimalPart && decimalPart.length > places) { 
      var roundingDigit = parseInt(decimalPart[places]); 
      var previousDigit = parseInt(decimalPart[places - 1]); 
      var increment = (roundingDigit < 5) ? 0 : 1; 
      previousDigit = previousDigit + increment; 
      decimalPart = decimalPart.replaceAt(places - 1, previousDigit + '').substr(0, places); 
     } 
     retVal = parseFloat(wholePart + '.' + decimalPart); 
    } 

    return retVal; 
} 

String.prototype.replaceAt = function (index, character) { 
    return this.substr(0, index) + character + this.substr(index + character.length); 
} 
+0

Возможно, было бы полезно сообщить, что ['toFixed()'] (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed) возвращает строку ...? – Teemu

+0

Это, к примеру, не имело значения. - спросил Джон с предупреждением. Вы всегда можете использовать parseFloat (string) для получения номера. – EnterSB

+0

К сожалению, toFixed возвращает строку, и нам нужно число, чтобы продолжить вычисления, и оно также имеет ту же ошибку. т. е. если вы поместите 625.185 в свой пример, к сожалению, это приведет к тому же неверному результату 625.18. –

3

Ваша проблема нелегко решить. Это происходит потому, что в IEEE double используется двоичное представление, которое не может точно представлять все десятичные числа. Ближайшим внутренним представлением 625.185 является 625.18499999999994543031789362430572509765625, который всегда немного меньше 625.185 и для которого правильное округление вниз.


В зависимости от обстоятельств, вы можете уйти со следующим:

Math.round(Math.round(625.185 * 1000)/10)/100 // evaluates to 625.19 

Это не совсем верно, однако, так как, например, он будет круглым, 625,1847 вверх 625,19. Используйте его, только если вы знаете, что на входе никогда не будет больше трех знаков после запятой.


Более простой вариант заключается в добавлении небольшого эпсилон до округления:

Math.round(625.185 * 100 + 1e-6)/100 

Это еще компромисс, так как вы могли бы очевидно иметь номер, который очень немного меньше, чем 625.185, но это, вероятно, более надежным, чем первое решение. Однако следите за отрицательными цифрами.

+0

Я думал добавить .0000001, я рад, что вы подтвердили его как действительный (хотя и слегка неточный) вариант. –

Смежные вопросы