【问题标题】:Why is 10000000000000.126.toString() 1000000000000.127 (and what can I do to prevent it)?为什么 10000000000000.126.toString() 1000000000000.127 (我能做些什么来防止它)?
【发布时间】:2011-07-03 10:07:46
【问题描述】:

为什么10000000000000.126.toString() 1000000000000.127 和100000000000.126.toString() 不是?

我认为它必须与 Js 中 Number 的最大值有关(根据this SO question),但这与浮点运算有关吗?

我之所以问这个问题,是因为我编写了这个函数来使用千位分隔符格式化一个数字,并希望防止这种情况发生。

function th(n,sep) {
    sep = sep || '.';
    var dec = n.toString().split(/[,.]/),
        nArr = dec[0].split(''),
        isDot = /\./.test(sep);
    return function tt(n) {
              return n.length > 3 ?
               tt(n.slice(0,n.length-3)).concat(n.slice(n.length-3).join('')) :
               [n.join('')]
            ;
        }(nArr)
        .join(sep)
        + (dec[1] ? (isDot?',':'.') + dec[1] : '');
}
sep1000(10000000000000.126); //=> 10.000.000.000.000,127
sep1000(1000000000000.126); //=> 1.000.000.000.000,126

【问题讨论】:

  • 这是因为我们的好老朋友浮点精度。
  • @Alnitak,我知道,但另外我正在寻找一种方法来防止它。我还在寻找相关的答案,真的。
  • @KooiInc:我完全不确定你可以,虽然你可以通过改变Number 试图表示的数字的大小来玩游戏,这可能会让你访问到更多数据。我已经用一个例子更新了我的答案(这很可能是有缺陷的)。

标签: javascript numbers floating-point


【解决方案1】:

因为不是所有的数字都可以用浮点数精确表示(JavaScript 使用双精度 64 位格式的 IEEE 754 数字),所以会出现舍入错误。例如:

alert(0.1 + 0.2); // "0.30000000000000004"

所有存储有限的编号系统(例如,所有编号系统)都有这个问题,但你我习惯于处理我们的十进制系统(不能准确表示“三分之一”),因此有些人感到惊讶计算机使用的浮点格式无法准确表示的不同值。这类事情就是为什么你会看到越来越多的“十进制”样式类型(Java 有 BigDecimal,C# 有 decimal 等),它们使用我们的数字表示风格(有代价)和因此对于需要舍入更符合我们预期的应用程序(例如金融应用程序)很有用。


更新:我没有尝试过,但您可以通过在获取字符串之前稍微操作一下值来解决此问题。例如,这适用于您的具体示例 (live copy):

代码:

function display(msg) {
  var p = document.createElement('p');
  p.innerHTML = msg;
  document.body.appendChild(p);
}

function preciseToString(num) {
  var floored = Math.floor(num),
      fraction = num - floored,
      rv,
      fractionString,
      n;

  rv = String(floored);
  n = rv.indexOf(".");
  if (n >= 0) {
    rv = rv.substring(0, n);
  }
  fractionString = String(fraction);
  if (fractionString.substring(0, 2) !== "0.") {
     return String(num); // punt
  }
  rv += "." + fractionString.substring(2);
  return rv;
}

display(preciseToString(10000000000000.126));

结果:

10000000000000.126953125

...当然,可以根据需要截断。当然,重要的是要注意10000000000000.126953125 != 10000000000000.126。但我认为那艘船已经航行了(例如,Number 已经包含一个不精确的值),因为您看到的是.127。我看不出有什么办法让你知道原来只去了三个地方,而不是Number

我并不是说上面的内容在任何方面都是可靠的,你必须真正通过各种步骤来证明它是可靠的(也就是说,我'm) 不要在那里做一些愚蠢的事情。再说一次,由于您不知道精度一开始在哪里结束,所以我不确定它有多大帮助。

【讨论】:

  • 谢谢 T.J.,我会去找你的答案。一直在修改您的(和其他代码),很难在结果字符串中显示 .126 部分。嗯,我想说这不是一个现实生活中的问题,但这是一个有趣的练习。
  • 这不是二进制/十进制问题。这是一个尝试将 17 位有效数字放入仅提供大约 16 位的类型的问题。
【解决方案2】:

这大约是浮点数可以存储的有效十进制数字的最大数量。

如果您查看http://en.wikipedia.org/wiki/IEEE_754-2008,您会发现双精度浮点数 (binary64) 可以存储大约 16 (15.95) 个十进制数字。

如果您的数字包含更多位数,您实际上会失去精度,您的示例就是这种情况。

【讨论】:

    猜你喜欢
    • 2021-09-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-11
    • 1970-01-01
    • 2014-12-05
    • 2016-02-09
    相关资源
    最近更新 更多