Почему оператор модуля возвращает дробное число в JavaScript?
Почему 49.90 % 0.10
в JavaScript вернуть 0.09999999999999581
? Я ожидал, что это будет 0.
8 ответов
Потому что в JavaScript используется математика с плавающей запятой, которая всегда приводит к ошибкам округления.
Если вам нужен точный результат с двумя десятичными знаками, умножьте свои числа на 100
перед операцией, а затем снова разделите:
var result = ( 4990 % 10 ) / 100;
Округлить при необходимости.
Я просто оставлю это здесь для дальнейшего использования, но здесь есть удобная функция, которая может более точно обрабатывать Remainder (так как 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>
Номер Javascript использует "двойную точность IEEE" для хранения значений. Они не способны точно хранить все десятичные числа. Результат не равен нулю из-за ошибки округления при преобразовании десятичного числа в двоичное.
49.90 = 49.89999999999999857891452848...
0.10 = 0.10000000000000000555111512...
Таким образом, пол (49,90 / 0,10) составляет всего 498, а остаток будет 0,09999....
Кажется, вы используете цифры для хранения суммы в долларах. Не делайте этого, поскольку операции с плавающей запятой распространяются и усиливают ошибку округления. Вместо этого сохраните число как количество центов. Целое число может быть представлено точно, и 4990 % 10
вернет 0.
причина
Плавающая точка не может хранить все десятичные значения точно. Поэтому при использовании форматов с плавающей запятой всегда будут ошибки округления входных значений. Ошибки на входах курса приводят к ошибкам на выходе. В случае дискретной функции или оператора на выходе может быть большая разница вокруг точки, где функция или оператор являются дискретными. Оператор по модулю дискретен, и ваш случай является ярким примером этой проблемы.
Ввод и вывод для значений с плавающей запятой
Таким образом, при использовании переменных с плавающей запятой вы всегда должны знать об этом. И любой вывод, который вы хотите получить из вычисления с плавающей запятой, всегда должен быть отформатирован / подготовлен перед отображением с учетом этого.
Когда используются только непрерывные функции и операторы, часто выполняется округление до желаемой точности (не усекайте). Стандартные функции форматирования, используемые для преобразования чисел с плавающей точкой, обычно делают это для вас.
Чтобы получить правильный вывод, основанный на ожидаемой точности ввода и желаемой точности вывода, вы также должны
- Округлите ввод с ожидаемой точностью или убедитесь, что никакие значения не могут быть введены с более высокой точностью.
- Перед округлением / форматированием добавьте небольшое значение к выходным данным, которое меньше или равно 1/4 требуемой точности и больше максимальной ожидаемой ошибки, вызванной ошибками округления на входе и во время расчета. Если это невозможно, комбинации точности используемого типа данных недостаточно, чтобы обеспечить желаемую точность вывода для ваших расчетов.
Эти две вещи часто не выполняются, и в большинстве случаев различия, вызванные их отсутствием, слишком малы, чтобы быть важными для большинства пользователей, но у меня уже был проект, в котором вывод не был принят пользователями без этих исправлений.
Дискретные функции или операторы (например, по модулю)
Когда задействованы дискретные операторы или функции, могут потребоваться дополнительные корректировки, чтобы убедиться, что выходные данные соответствуют ожидаемым. Округление и добавление небольших исправлений перед округлением не может решить проблему.
Может потребоваться специальная проверка / исправление промежуточных результатов расчета сразу после применения дискретной функции или оператора.
Конкретный случай этого вопроса
В этом случае вы ожидаете ввод с определенной точностью, поэтому можно скорректировать вывод для влияния ошибок округления, которые намного меньше, чем требуемая точность.
Если мы говорим, точность вашего типа данных е.
Ваш ввод будет сохранен не как введенные вами значения a и b, а как a*(1+/-e) и b*(1+/-e)
Результат деления a*(1+/-e) на b*(1+/-e) приведет к (a / b) (1 +/- 2e).
Функция по модулю должна обрезать результат и снова умножить. Таким образом, результатом будет (a / b b) (1 +/- 3e) = a (1 +/- 3e), что приведет к ошибке a*3e.
Мод добавляет *e к возможной ошибке a * 3e из-за вычитания 2 значений с возможными ошибками a * 3e и a*e.
Поэтому вам следует проверить, что суммарная возможная ошибка a*4e меньше требуемой точности, и если это условие выполнено и результат отличается не более чем от b, чем максимально возможная ошибка, вы можете смело заменить ее на 0.
Лучше избегать проблем
Часто более эффективно избегать этих проблем, используя типы данных (целочисленные или фиксированные форматы) для таких вычислений, которые могут хранить ожидаемый ввод без ошибок округления. Примером этого является то, что вы никогда не должны использовать значения с плавающей запятой для финансовых расчетов.
Посмотрите на плавающие точки и их недостатки - число, подобное 0.1
не может быть правильно сохранен как число с плавающей запятой, поэтому всегда будут такие проблемы. Возьмите свои числа *10 или *100 и вместо этого выполняйте вычисления с целыми числами.
http://en.wikipedia.org/wiki/Modulo_operation Не сердитесь, по модулю используются целые числа ^^ Так что плавающие значения вызывают некоторые ошибки.
Надеюсь, это будет полезно для кого-то:
let asBuffer = new ArrayBuffer(8);
let asInt = new Uint32Array(asBuffer );
let asFloat = new Float32Array(asBuffer );
asFloat.set([49.9,.1]);
asInt.set([asFloat[0]/asFloat[1], asFloat[0]%asFloat[1]]);
Результат
asInt Uint32Array(2) [499, 0, buffer: ArrayBuffer(8), byteLength: 8, byteOffset: 0, length: 2, Symbol(Symbol.toStringTag): 'Uint32Array']
//In general I'm moving more and more towards
//Using typed arrays to store values in JS.
Это не идеальный ответ, но он работает.
function format_float_bug(num)
{
return parseFloat( num.toFixed(15) );
}
Вы можете использовать следующим образом,
format_float_bug(4990 % 10);
потому что ниже числа (49.89999999999999857891452848) первые 15 десятичных знаков похожи на 9999999