Javascript的toFixed()方法未按预期工作

3

我正在使用toFixed方法,但是该方法并没有按照预期运行。

parseFloat(19373.315).toFixed(2);

//19373.31   Chrome 

期望输出:19373.32

parseFloat(9373.315).toFixed(2);
// 9373.32  Working fine

为什么第一个例子会向下取整,而第二个例子会向上取整?

4
浮点数计算是否有问题? - undefined
1
试一下这个。toFixed 是依赖于浏览器的:https://dev59.com/J3RB5IYBdhLWcg3wn4jc. - undefined
@JaromandaX:抱歉,我没明白你的意思。 - undefined
确实。奇怪的是... - undefined
1
我在暗示这个网址:https://dev59.com/G3RB5IYBdhLWcg3wj36c - undefined
显示剩余3条评论
5个回答

6
问题在于大多数十进制小数的二进制浮点表示并不精确。19373.315 的内部表示实际上可能类似于 19373.314999999,所以 toFixed 向下舍入,而 19373.315 可能是 19373.315000001,这会向上舍入。

假设toFixed将转换为32位浮点数;https://www.h-schmidt.net/FloatConverter/IEEE754.html 19373.315 -> 19373.314453125(误差为-0.000546875) - undefined
尽管(19373.315).toFixed(4)的结果是19373.3150,但我仍然认为这是一个错误,即使这是“预期”的或“有意的”。在转换为固定字符串时,应该使用双精度和正确的四舍五入。我认为规范甚至也是这样说的。:\ - undefined
我将我的评论和研究整合成一个答案,并提供了一个“修复”的解决方法。 - undefined

1
为什么第一个例子向下取整,而第二个例子向上取整?
查看两个值在内存中的二进制表示。

const farr = new Float64Array(2);
farr[0] = 19373.315;
farr[1] = 9373.315;
const uarr = new Uint32Array(farr.buffer);
console.log(farr[0], uarr[1].toString(2).padStart(32, 0) + uarr[0].toString(2).padStart(32, 0));
console.log(farr[1], uarr[3].toString(2).padStart(32, 0) + uarr[2].toString(2).padStart(32, 0));

不用深入细节,我们可以看到第二个值在末尾多了一个“1”,而当它放入64位时,这个“1”在第一个更大的值中丢失了。


1
@TylerY86 哎呀,这让我想起了另一个Chrome的bug,幸运的是现在已经修复了。 - undefined
我认为这个 bug 的精度比这个要差一个数量级(-0.998)。尽管如此,它仍然输出 0.0022002200220022002200220022002201。哈哈 - undefined
1
也许规范对于toFixed函数的指令过于具体,导致了一个错误?(火狐浏览器也有这个问题。)哈哈 - undefined
^-0.998 应该是 0.098 - undefined
@TylerY86 是的,这个问题最终在某个版本的V8中得到了修复,现在toString函数似乎又正常工作了。 - undefined
显示剩余2条评论

0

假定 toFixed 转换为 32 位浮点数; 使用此实用程序进行检查...

19373.315 在 32 位浮点格式中存储为 19373.314453125(误差为 -0.000546875)。

尽管 (19373.315).toFixed(4) 的结果为 19373.3150

即使这是“预期的”或“意图的”,我仍会将其报告为一个错误。

应该在舍入检查过程中使用双精度浮点数,以便在转换为固定字符串时进行正确的舍入。

我认为规范甚至都这样说。:\

在 V8 JavaScript 引擎源码中,Number.prototype.toFixed 函数调用了 此文件 中的 DoubleToFixedCString...

可能存在一些不适当的优化...(正在调查中。)

我建议提交一个V8的附加测试用例,具体是19373.315。

(19373.3150).toFixed(39)得到的结果是19373.314999999998690327629446983337402343750。

在将其四舍五入到2位小数时,只进行了一次舍入,使其变为19373.315 - 这是正确的,但不是在正确的数字上。

我认为这里应该再进行一次舍入,以捕捉这种边缘情况。我认为可能需要先舍入到n+1位小数,然后再舍入到n位小数。也许还有其他聪明的方法来修复它。

function toFixedFixed(a,n) {
  return (a|0) + parseFloat((a % 1).toFixed(n+1)).toFixed(n).substr(1);
}

console.log(toFixedFixed(19373.315,2)); // "19373.32"
console.log(toFixedFixed(19373.315,3)); // "19373.315"
console.log(toFixedFixed(19373.315,4)); // "19373.3150"
console.log(toFixedFixed(19373.315,37)); // "19373.3149999999986903276294469833374023438"
console.log(toFixedFixed(19373.315,38)); // "19373.31499999999869032762944698333740234375"
console.log(toFixedFixed(19373.315,39)); // "19373.314999999998690327629446983337402343750"

(摘自我对Vahid Rahmani回答的评论,他是正确的。)

0
其他答案已经解释了为什么,我建议使用一个类似于numeral.js的库,它会按照你期望的方式进行四舍五入。

0
这对我有用。source
export const preciseRound = (value, exp) => {
  /**
   * Decimal adjustment of a number.
   *
   * @param   {String}    type    The type of adjustment.
   * @param   {Number}    value   The number.
   * @param   {Integer}   exp     The exponent (the 10 logarithm of the adjustment base).
   * @returns {Number}            The adjusted value.
   */
  function decimalAdjust(type, value, exp) {
    // If the exp is undefined or zero...
    if (typeof exp === "undefined" || +exp === 0) {
      return Math[type](value);
    }
    value = +value;
    exp = +exp;
    // If the value is not a number or the exp is not an integer...
    if (isNaN(value) || !(typeof exp === "number" && exp % 1 === 0)) {
      return NaN;
    }
    // Shift
    value = value.toString().split("e");
    value = Math[type](+(value[0] + "e" + (value[1] ? +value[1] - exp : -exp)));
    // Shift back
    value = value.toString().split("e");
    return +(value[0] + "e" + (value[1] ? +value[1] + exp : exp));
  }

  // You can use floor, ceil or round
  return decimalAdjust("round", value, exp);
};

console.log(preciseRound(1.005, -2));  // <= 1.01
console.log(preciseRound(1.341, -2));  // <= 1.34
console.log(preciseRound(1.01, -2));   // <= 1.01
console.log(preciseRound(33.355, -2)); // <= 33.36

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接