为什么 JavaScript 中模数运算符返回小数?

发布于 2024-09-27 17:17:41 字数 96 浏览 4 评论 0原文

为什么 JavaScript 中的 49.90 % 0.10 返回 0.099999999999999581?我预计它会是0。

Why does 49.90 % 0.10 in JavaScript return 0.09999999999999581? I expected it to be 0.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

回忆躺在深渊里 2024-10-04 17:17:41

因为 JavaScript 使用浮点数学,这可能会导致舍入错误。

如果您需要精确到小数点后两位的结果,请在运算前将数字乘以 100,然后再除以:

var result = ( 4990 % 10 ) / 100;

如有必要,请进行四舍五入。

Because JavaScript uses floating point math which can lead to rounding errors.

If you need an exact result with two decimal places, multiply your numbers with 100 before the operation and then divide again afterwards:

var result = ( 4990 % 10 ) / 100;

Round if necessary.

你的心境我的脸 2024-10-04 17:17:41

我将其留在这里以供将来参考,但这里有一个方便的函数,可以更精确地处理 余数 (自 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>

I'll just leave this here for future reference, but here is a handy function that can more precisely handle Remainder (since JS doesn't have a modulo operator) involving floats.

  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>

酒废 2024-10-04 17:17:41

Javascript 的 Number 使用“IEEE 双精度”来存储值。它们无法准确存储所有十进制数。由于将十进制数转换为二进制数时存在舍入误差,结果不为零。

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

因此,floor(49.90 / 0.10) 只有 498,余数将为 0.09999....


看来您正在使用数字来存储美元金额。 不要这样做,作为浮点运算传播并放大舍入误差。而是将数字存储为美分。整数可以精确表示,4990 % 10 将返回 0。

Javascript's Number is using "IEEE double-precision" to store the values. They are incapable of storing all decimal numbers exactly. The result is not zero because of round-off error when converting the decimal number to binary.

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

Thus floor(49.90 / 0.10) is only 498, and the remainder will be 0.09999....


It seems that you are using numbers to store amount of dollars. Don't do this, as floating point operations propagate and amplify the round-off error. Store the number as amount of cents instead. Integer can be represented exactly, and 4990 % 10 will return 0.

简单爱 2024-10-04 17:17:41

原因

浮点无法准确存储所有十进制值。因此,当使用浮点格式时,输入值总是会出现舍入误差。
输入上的错误当然会导致输出上的错误。
对于离散函数或运算符,在函数或运算符离散的点周围的输出可能会有很大差异。
模运算符是离散的,您的案例显然就是这个问题的一个例子。

浮点值的输入和输出

因此,在使用浮点变量时,您应该始终注意这一点。无论您想要从浮点计算中获得什么输出,在显示之前都应始终对其进行格式化/调节,并牢记这一点。
当仅使用连续函数和运算符时,通常会舍入到所需的精度(不要截断)。用于将浮点数转换为字符串的标准格式化功能通常会为您完成此操作。
要根据预期的输入精度和所需的输出精度获得正确的输出,您还应该

  • 将输入舍入到预期的精度,或者确保无法输入更高精度的值。
  • 在对输出进行舍入/格式化之前,向输出添加一个小值,该值小于或等于所需精度的 1/4,并且大于由输入和计算过程中的舍入误差引起的最大预期误差。如果不可能,则所用数据类型的精度组合不足以为您的计算提供所需的输出精度。

这两件事通常没有完成,并且在大多数情况下,不做它们所造成的差异对于大多数用户来说太小而不重要,但我已经有一个项目,如果没有这些更正,用户就不会接受输出。

离散函数或运算符(如模)

当涉及离散运算符或函数时,可能需要额外的更正以确保输出符合预期。四舍五入并在四舍五入之前添加小修正并不能解决问题。
在应用离散函数或运算符之后,可能需要立即对中间计算结果进行特殊检查/更正。

此问题的具体情况

在这种情况下,您期望输入具有一定的精度,因此可以针对远小于所需精度的舍入误差的影响来纠正输出。

如果我们说你的数据类型的精度是e。
您的输入不会存储为您输入的值 a 和 b,而是存储为 a*(1+/-e) 和 b*(1+/-e)
a*(1+/-e) 除以 b*(1+/-e) 的结果将得到 (a/b)(1+/-2e)。
modula 函数必须截断结果并再次相乘。
所以结果将是 (a/b
b)(1+/-3e) = a(1+/-3e) 导致 a*3e 的错误。
mod 将 a*e 添加到 a*3e 的可能误差中,因为减去了 2 个值,可能的误差为 a*3e 和 a*e。
因此,您应该检查总可能误差 a*4e 是否小于所需精度,如果满足该条件并且结果与 b 的差异不超过最大可能误差,则可以安全地将其替换为 0。

更好避免出现问题

通常通过使用数据类型(整数或定点格式)进行此类计算可以更有效地避免这些问题,这样可以存储预期的输入而不会出现舍入错误。
一个例子是,您永远不应该使用浮点值进行财务计算。

Cause

Floating point can't store all decimal values exactly. So when using floating point formats there will always be rounding errors on the input values.
The errors on the inputs of course results on errors on the output.
In case of a discrete function or operator there can be a big difference on the output around the point where the function or operator is discrete.
The modula operator is discrete and your case is clearly an example of this problem.

Input and output for floating point values

So, when using floating point variables, you should always be aware of this. And whatever output you want from a calculation with floating points should always be formatted/conditioned before displaying with this in mind.
When only continuous functions and operators are used, rounding to the desired precision often will do (don't truncate). Standard formatting features used to convert floats to string will usually do this for you.
To have a correct output based on expected precision of inputs and desired precision of output, you should also

  • Round inputs to the expected precision or make sure no values can be entered with higher precision.
  • Add a small value to the outputs before rounding/formatting them which is smaller than or equal to 1/4 of the desired precision and bigger than the maximum expected error caused by rounding errors on input and during calculation. If that is not possible the combination of the precision of the used data type isn't enough to deliver the desired output precision for your calculation.

These 2 things are often not done and in most cases the differences caused by not doing them are too small to be important for most users, but I already had a project where output wasn't accepted by the users without those corrections.

Discrete functions or operators (like modula)

When discrete operators or functions are involved, extra corrections might be required to make sure the output is as expected. Rounding and adding small corrections before rounding can't solve the problem.
A special check/correction on intermediate calculation results, immediately after applying the discrete function or operator might be required.

Specific case of this question

In this case, you expect input with a certain precision, so it is possible to correct output for impact of rounding errors which are a lot smaller than the desired precision.

If we say the precision of your data type is e.
Your input will not be stored as the values a and b you entered, but as a*(1+/-e) and b*(1+/-e)
The result of a division a*(1+/-e) by b*(1+/-e) would result in (a/b)(1+/-2e).
The modula function has to truncate the result and multiply again.
So the result will be (a/b
b)(1+/-3e) = a(1+/-3e) resulting in an error of a*3e.
The mod adds a*e to the possible error of a*3e because of the subtraction of 2 values with a possible errors of a*3e and a*e.
So you should check that the total possible error a*4e is smaller than the desired precision and if that condition is met and the result differs no more from b than that maximum possible error, you can safely replace it by 0.

Better avoid having the problem

It is often more efficient to avoid these problems by using data types (integer or fixed point formats) for calculations like this which can store the expected input without rounding errors.
An example of that is that you should never use floating point values for financial calculations.

披肩女神 2024-10-04 17:17:41

看看浮点数及其缺点 - 像 0.1 这样的数字无法正确保存为浮点数,所以总会出现这样的问题。将您的数字 *10 或 *100 并用整数进行计算。

Take a look at floating points and its disadvantages - a number like 0.1 can't be saved correctly as floating point, so there will always be such problems. Take your numbers *10 or *100 and do the calculations with integers instead.

樱花细雨 2024-10-04 17:17:41

http://en.wikipedia.org/wiki/Modulo_operation
别生气,模与整数一起使用^^
所以浮动值会出现一些错误。

http://en.wikipedia.org/wiki/Modulo_operation
Don't be angry modulo is used with integers ^^
So floating values occure some errors.

厌味 2024-10-04 17:17:41

希望这对某人有帮助:

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, 缓冲区: ArrayBuffer(8), 
字节长度:8,字节偏移:0,长度:2, 
符号(Symbol.toStringTag):'Uint32Array']
//In general I'm moving more and more towards 
//Using typed arrays to store values in JS.

Hopefully this is helpful to someone:

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]]);

Result

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.
要走干脆点 2024-10-04 17:17:41

我发现了这个关于 Modulo with Javascript 行为的问题。
我正在寻找为什么我在玩百分比时遇到类似问题的原因。

我的回答并没有真正回答有关模 0.1 的问题,但它帮助我解决了我的问题。
为了分享我的修复,我添加了这个示例。

就我而言,我只想记录十的倍数的完成百分比。
没有任何值等于 0,因此没有打印任何内容。
我这样做是为了通过执行 Math.round(percentage) % 10 === 0 来解决我的问题。这将在进行取模之前仅保留最接近的整数。

希望我的例子可以帮助其他面临同样问题的人。

I found this question regarding the behavior of Modulo with Javascript.
I was looking for the reason why I had similar issue playing with percentage.

My answer is not really answering the question regarding the modulo 0.1 but it help me fix my issue.
In order to share my fix I'm adding this example.

In my case I wanted to log percentage of completion only on multiple of ten.
No value ever equal 0 so nothing was printed.
I did this to resolve my issue by doing Math.round(percentage) % 10 === 0. This will keep only the closest integer before doing the modulo.

Hope my example can help other people facing the same issue.

月依秋水 2024-10-04 17:17:41

这不是一个完美的答案,但它确实有效。

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

您可以按如下方式使用,

format_float_bug(4990 % 10);

因为下面的数字 (49.89999999999999857891452848) 前 15 个小数位就像 9999999

This is not a perfect answer but it works.

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

you can use as follows,

format_float_bug(4990 % 10);

because below number (49.89999999999999857891452848) first 15 decimal places are like 9999999

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文