34

为什么49.90 % 0.10在 JavaScript 中返回0.09999999999999581?我预计它是0。

4

7 回答 7

57

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

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

var result = ( 4990 % 10 ) / 100;

必要时进行圆形。

于 2010-10-19T08:28:44.253 回答
20

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

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

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


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

于 2010-10-19T08:32:10.073 回答
18

我将把它留在这里以供将来参考,但这里有一个方便的函数,可以更精确地处理涉及浮点数的余数(因为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>

于 2015-07-29T21:00:35.147 回答
2

原因

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

浮点值的输入和输出

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

  • 将输入四舍五入到预期的精度,或者确保不能以更高的精度输入任何值。
  • 在舍入/格式化输出之前向输出添加一个小值,该值小于或等于所需精度的 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 的可能误差中,因为减去了 a*3e 和 a*e 的可能误差的 2 个值。
因此,您应该检查可能的总误差 a*4e 是否小于所需的精度,如果满足该条件并且结果与 b 的差异不超过最大可能误差,则可以安全地将其替换为 0。

最好避免出现问题

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

于 2017-07-27T09:24:47.373 回答
1

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

于 2010-10-19T08:30:35.310 回答
0

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

于 2010-10-19T08:29:44.177 回答
-3

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

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

你可以使用如下,

format_float_bug(4990 % 10);

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

于 2014-02-17T11:16:23.720 回答