Math.round(num) 与 num.toFixed(0) 和浏览器不一致

IT技术 javascript cross-browser
2021-01-27 09:01:57

考虑以下代码:

for (var i=0;i<3;i++){
   var num = i + 0.50;
   var output = num + " " + Math.round(num) + " " + num.toFixed(0);
   alert(output);
}

在 Opera 9.63 中,我得到:

0.5 1 0

1.5 2 2

2.5 3 2

在 FF 3.03 中,我得到:

0.5 1 1

1.5 2 2

2.5 3 3

在 IE 7 中,我得到:

0.5 1 0

1.5 2 2

2.5 3 3

注意加粗的结果。为什么会出现这种不一致?这是否意味着toFixed(0)应该避免?将数字四舍五入到最接近的整数的正确方法是什么?

6个回答

编辑:要回答您的编辑,请使用Math.roundNumber如果您喜欢这种语法,您还可以对对象进行原型设计,让它按照您的要求进行。

Number.prototype.round = function() {
  return Math.round(this);
}
var num = 3.5;
alert(num.round())

我以前从未使用Number.toFixed()过(主要是因为大多数 JS 库都提供了一种toInt()方法),但是从您的结果来看,我会说使用Math方法 ( round, floor, ceil)会更一致那么toFixed如果您正在寻找跨浏览器一致性.

修改本机对象(即不是不好的做法Number吗?
2021-04-01 09:01:57
是的; 在大多数情况下,出于多种原因,修改本机实现被认为是不好的,主要是处理模糊性和由此产生的意外行为,尤其是对于其他或未来的团队成员。我建议通过 mixin 函数/工厂或通过编写您自己的自定义“类型”来扩展您希望修改的对象,该“类型”指向(在这种情况下)Number.prototype 的实例。
2021-04-01 09:01:57
同样重要的是,Math.round 的速度非常快:Math.round()几乎比.toFixed( jsperf.com/math-round-vs-tofixed2快 500% )。
2021-04-07 09:01:57

要解决您的两个原始问题/问题:

Math.round(num) 与 num.toFixed(0)

这里的问题在于错误地认为这些应该总是给出相同的结果。事实上,它们受不同的规则支配。例如,看看负数。因为Math.round使用“四舍五入”作为规则,您将看到即使Math.round(-1.5)计算为-1Math.round(1.5)计算为2

Number.prototype.toFixed,另一方面,根据规范的第 6 步,使用基本上等同于“距零的一半”作为规则,这基本上是说将负数视为正数,然后在结尾。因此,是所有符合规范的浏览器中的真实陈述。请注意,这些值是字符串,而不是数字。此外,值得注意的是这两个(数量),由于运算符优先级。(-1.5).toFixed(0) === "-2"(1.5).toFixed(0) === "2"-1.5.toFixed(0)-(1.5).toFixed(0)=== -2

浏览器不一致

大多数现代浏览器——或者至少是你在撰写本文时可能会支持的 浏览器,除了IE——都应该正确实现规范。(根据Renee 的评论toFixed您在 Opera 中指出问题已得到修复,大概是因为他们开始使用与 Chrome 相同的 JS 引擎。)仍然值得重申的是,即使规范在所有浏览器中一致实施,定义的行为在规范中,特别是对于toFixed舍入,对于期望真正数学准确性的“凡人”JS 开发人员来说仍然有点不直观——参见Javascript toFixed Not Rounding这个在 V8 JS 引擎上提交的“按预期工作”的错误示例.

结论

简而言之,这是两个不同的函数,具有两种不同的返回类型和两组不同的舍入规则。

正如其他人所建议的那样,我还想说“使用适合您特定用例的任何函数”(特别注意 的特性toFixed,尤其是 IE 的错误实现)。正如其他人所提到Math.round/ceil/floor我个人更倾向于推荐 , 的一些明确组合 编辑: ...不过,在返回并阅读您的说明之后,您的用例(四舍五入为整数)肯定会调用恰当命名的Math.round函数。

我澄清了我的结论和答案,这应该与链接问题的最佳答案一致。toFixed不是马车,只是非常不直观。
2021-03-16 09:01:57
toFixed 规范是否含糊不清?我试图了解 IE 11 和 Chrome 42 之间的不同结果如何不是错误。在 IE 上,1.555.toFixed(2) 产生“1.56”,而在 Chrome 上它产生“1.55”。链接的 SO 问题的最佳答案开头是,“JavaScript 的 .toFixed() 函数非常有问题。” 我花时间详细说明这些的原因是因为您的陈述,“规范在 [现代] 浏览器中一致实施”,要么是错误的,要么具有危险的误导性。
2021-03-20 09:01:57
你对 IE 的看法是完全正确的,@pettys——我只用 OP 给出的值进行了测试。在玩过其他一些值之后,它似乎是唯一一个做一些不同/错误的浏览器(我已经测试过)。再次更新并澄清了我的答案——谢谢!
2021-03-20 09:01:57
您的结论部分及其链接的问题似乎与您的其余答案相矛盾。
2021-03-23 09:01:57

我认为 FF 对 toFixed 做了正确的事情,因为下面的第 10 步说“如果有两个这样的 n,请选择较大的 n。”

正如Grant Wagner所说:使用Math.ceil(x)Math.floor(x)而不是x.toFixed()

以下所有内容均来自ECMAScript 语言规范

15.7.4.5 Number.prototype.toFixed (fractionDigits)

返回一个字符串,其中包含以定点表示法表示的fractionDigits数字,小数点后数字。如果 fractionDigits未定义,0则假定。具体执行以下步骤:

  1. 我们fToInteger(fractionDigits)(如果fractionDigits未定义,则此步骤生成值0)。
  2. 如果f < 0f > 20,则抛出RangeError异常。
  3. x是这个数值。
  4. 如果xNaN,则返回字符串"NaN"
  5. s是空字符串。
  6. 如果x ≥ 0,请转到步骤 9。
  7. 让我们吧"-"
  8. x = –x.
  9. 如果x ≥ 10^21,让m = ToString(x)并转到步骤 20。
  10. n是一个整数,其精确的数学值 n ÷ 10^f – x尽可能接近于零。如果有两个这样的n,选择较大的n
  11. 如果n = 0,让m成为字符串"0"否则, letm是由十进制表示的数字组成的字符串n(按顺序,没有前导零)。
  12. 如果f = 0,转到步骤 20。
  13. k成为 中的字符数m
  14. 如果k > f,请转到步骤 18。
  15. z成为由f+1–k出现的字符组成的字符串'0'
  16. m成为字符串z的串联m
  17. k = f + 1.
  18. a成为 的第一个k–f字符m,让b成为 的其余f字符m
  19. m是三个字符串的串联a"."b
  20. 返回字符串s的串联m

方法length属性toFixed1

如果toFixed使用多个参数调用方法,则行为未定义(参见第 15 节)。

toFixed对于fractionDigits小于0或大于 的值,允许实现扩展 的行为20在这种情况下 toFixed不一定会抛出RangeError这样的值。

注意的输出toFixed可能比toString某些值更精确,因为toString只打印足够的有效数字以将数字与相邻的数字值区分开来。例如, (1000000000000000128).toString()返回"1000000000000000100",而 (1000000000000000128).toFixed(0)返回"1000000000000000128"

天哪,那些 ecmascript 的人以前做过标准吗?:那个标准写得很糟糕,而且是一个完整的类:从字面上看,我从来没有见过任何标准甚至接近这个烂摊子。如果可能,请重写引用的文本,而不是将意大利面条批量加载到答案中。
2021-03-24 09:01:57
我完全同意@Pacerier。你碰巧有好的规范的例子供参考吗?
2021-04-01 09:01:57

toFixed()返回一个字符串值。来自 Javascript:权威指南

将数字转换为包含小数位后指定位数的字符串。

Math.round()返回一个整数。

显然, toFixed() 似乎更适合用于金钱,例如,

'$' + 12.34253.toFixed(2) = '$12.34'

很遗憾toFixed()似乎没有正确舍入

公平地说,有多种舍入方法:en.wikipedia.org/wiki/Rounding
2021-03-17 09:01:57

而不是toFixed(0)使用Math.ceil()Math.floor(),具体取决于需要什么。