Почему оператор модуля возвращает дробное число в JavaScript? - PullRequest
29 голосов
/ 19 октября 2010

Почему 49.90 % 0.10 в JavaScript возвращает 0.09999999999999581?Я ожидал, что это будет 0.

Ответы [ 7 ]

50 голосов
/ 19 октября 2010

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

Если вам нужен точный результат с двумя десятичными знаками, умножьте свои числа на 100 перед операцией, а затем снова разделите:

var result = ( 4990 % 10 ) / 100;

Округлить при необходимости.

18 голосов
/ 19 октября 2010

Javascript's Number использует «двойную точность IEEE» для хранения значений.Они не способны точно хранить все десятичные числа.Результат не равен нулю из-за ошибки округления при преобразовании десятичного числа в двоичное.

49.90 = 49.89999999999999857891452848...
 0.10 =  0.10000000000000000555111512...

Таким образом, пол (49,90 / 0,10) составляет всего 498, а остаток будет 0,09999 ....


Кажется, вы используете цифры для хранения суммы в долларах. Не делайте этого , поскольку операции с плавающей запятой распространяются и усиливают ошибку округления.Вместо этого сохраните число как сумму центов .Целое число может быть представлено точно, и 4990 % 10 вернет 0.

12 голосов
/ 30 июля 2015

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

  function floatSafeRemainder(val, step){
    var valDecCount = (val.toString().split('.')[1] || '').length;
    var stepDecCount = (step.toString().split('.')[1] || '').length;
    var decCount = valDecCount > stepDecCount? valDecCount : stepDecCount;
    var valInt = parseInt(val.toFixed(decCount).replace('.',''));
    var stepInt = parseInt(step.toFixed(decCount).replace('.',''));
    return (valInt % stepInt) / Math.pow(10, decCount);
  }

$(function() {
  
  
  function floatSafeModulus(val, step) {
    var valDecCount = (val.toString().split('.')[1] || '').length;
    var stepDecCount = (step.toString().split('.')[1] || '').length;
    var decCount = valDecCount > stepDecCount ? valDecCount : stepDecCount;
    var valInt = parseInt(val.toFixed(decCount).replace('.', ''));
    var stepInt = parseInt(step.toFixed(decCount).replace('.', ''));
    return (valInt % stepInt) / Math.pow(10, decCount);
  }
  
  
  $("#form").submit(function(e) {
    e.preventDefault();
    var safe = 'Invalid';
    var normal = 'Invalid';
    var var1 = parseFloat($('#var1').val());
    var var2 = parseFloat($('#var2').val());
    if (!isNaN(var1) && !isNaN(var2)) {
      safe = floatSafeModulus(var1, var2);
      normal = var1 % var2
    }
    $('#safeResult').text(safe);
    $('#normalResult').text(normal);
  });
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<form id="form" novalidate>
  <div>
    <input type="number" id="var1">%
    <input type="number" id="var2">
  </div>
  <div>safe: <span id="safeResult"></span><div>
  <div>normal (%): <span id="normalResult"></span></div>
  <input type="submit" value="try it out">
</form>
2 голосов
/ 27 июля 2017

Причина

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

Ввод и вывод для значений с плавающей запятой

Итак, при использовании переменных с плавающей запятой вывсегда должен знать об этом.И любой вывод, который вы хотите получить из вычисления с плавающей запятой, всегда должен быть отформатирован / обработан перед отображением с учетом этого.
Когда используются только непрерывные функции и операторы, часто выполняется округление до желаемой точности (не обрезать),Стандартные функции форматирования, используемые для преобразования чисел с плавающей запятой, обычно делают это для вас.
Чтобы иметь правильный вывод, основанный на ожидаемой точности ввода и желаемой точности вывода, вы также должны

  • Округлые вводыс ожидаемой точностью или убедитесь, что никакие значения не могут быть введены с более высокой точностью.
  • Перед округлением / форматированием добавьте небольшое значение к выводам, которое меньше или равно 1/4 от требуемой точности и большечем максимальная ожидаемая ошибка, вызванная ошибками округления на входе и во время расчета.Если это невозможно, комбинации точности используемого типа данных недостаточно, чтобы обеспечить желаемую точность вывода для ваших расчетов.

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

Дискретные функции или операторы (например, по модулю)

Когда задействованы дискретные операторы или функции, могут потребоваться дополнительные исправления, чтобы обеспечить выводкак и ожидалось.Округление и добавление небольших поправок перед округлением не может решить проблему.
Может потребоваться специальная проверка / исправление промежуточных результатов вычислений сразу после применения дискретной функции или оператора.

Особый случайэтого вопроса

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

Если мы говорим, что точность вашего типа данных равна e.
Ваш ввод будет сохранен не как введенные вами значения a и b, а как * (1 +/- e) и b * (1 + /).-e)
Результат деления a * (1 +/- e) на b * (1 +/- e) приведет к (a / b) (1 +/- 2e).
Функция по модулю должна обрезать результат и снова умножить.Таким образом, результат будет (a / b
b) (1 +/- 3e) = a (1 +/- 3e), что приведет к ошибке * 3e.
Мод добавляет * e квозможная ошибка a * 3e из-за вычитания 2 значений с возможными ошибками a * 3e и a * e.
Таким образом, вы должны убедиться, что общая возможная ошибка a * 4e меньше требуемой точности, и еслиусловие выполнено, и результат отличается не более чем от b от максимально возможной ошибки, вы можете смело заменить ее на 0.

Лучше избежать возникновения проблемы

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

1 голос
/ 19 октября 2010

Взгляните на с плавающей точкой и его недостатки - число типа 0.1 не может быть правильно сохранено как число с плавающей запятой, поэтому всегда будут такие проблемы. Возьмите свои числа * 10 или * 100 и сделайте вычисления с целыми числами.

1 голос
/ 19 октября 2010

http://en.wikipedia.org/wiki/Modulo_operation Не сердись по модулю используется с целыми числами ^^ Таким образом, плавающие значения вызывают некоторые ошибки.

0 голосов
/ 17 февраля 2014

Это не идеальный ответ, но он работает.

function format_float_bug(num)
{
   return parseFloat( num.toFixed(15) ); 
} 

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

format_float_bug(4990 % 10);

потому что ниже числа (49.89999999999999857891452848) первые 15 десятичных знаков похожи на 9999999

...