本文最初发表于David Kaye博客
我在所有尝试过的JavaScript环境(Chrome、Firefox、Internet Explorer、Brave和Node.js)中都发现了Number().toFixed()中的舍入错误。令人惊讶的是,修复方法非常简单。继续阅读……
在修改一个执行与Intl.NumberFormat#format()相同功能的数字格式化函数时,我发现了toFixed()中此版本的舍入错误。
<code class="language-javascript">(1.015).toFixed(2) // 返回 "1.01" 而不是 "1.02"</code>
失败的测试位于此处第42行。直到2017年12月我才发现它,这促使我去检查其他问题。
请参阅我关于此问题的推文:
关于使用toFixed()的舍入错误,有很长的错误报告历史。
以下是一些关于此问题的StackOverflow问题的简短示例:
通常,这些都指出了一个值的一个错误,但没有报告返回错误结果的值的范围或模式(至少我没有找到,我可能错过了什么)。这使得程序员只能关注小问题而看不到更大的模式。我不责怪他们这样。
基于输入的意外结果必须源于输入中的共享模式。因此,我没有审查Number().toFixed()的规范,而是专注于使用一系列值进行测试,以确定错误在每个系列中的出现位置。
我创建了以下测试函数,以对一系列从1到maxValue的整数进行toFixed()运算,为每个整数添加例如.005的小数。toFixed()的固定(数字位数)参数根据小数值的长度计算。
<code class="language-javascript">(1.015).toFixed(2) // 返回 "1.01" 而不是 "1.02"</code>
以下示例对整数1到128执行操作,为每个整数添加小数.015,并返回一个“意外”结果数组。每个结果包含given、expected和actual字段。在这里,我们使用该数组并打印每个项目。
<code class="language-javascript">function test({fraction, maxValue}) { fraction = fraction.toString() var fixLength = fraction.split('.')[1].length - 1 var last = Number(fraction.charAt(fraction.length - 1)) var fixDigit = Number(fraction.charAt(fraction.length - 2)) last >= 5 && (fixDigit = fixDigit + 1) var expectedFraction = fraction.replace(/[\d]{2,2}$/, fixDigit) return Array(maxValue).fill(0) .map(function(ignoreValue, index) { return index + 1 }) .filter(function(integer) { var number = integer + Number(fraction) var actual = number.toFixed(fixLength) var expected = Number(number + '1').toFixed(fixLength) return expected != actual }) .map(function(integer) { var number = Number(integer) + Number(fraction) return { given: number.toString(), expected: (Number(integer.toFixed(0)) + Number(expectedFraction)).toString(), actual: number.toFixed(fixLength) } }) }</code>
对于这种情况,有6个意外结果。
<code class="language-javascript">test({ fraction: .015, maxValue: 128 }) .forEach(function(item) { console.log(item) })</code>
我发现此错误包含三个部分:
例如,对于整数1到128,(value).toFixed(2)使用以5结尾的不同3位小数,产生以下结果:
那些比我更了解二进制和浮点数学知识的人可能能够推断出根本原因。我把这留给读者作为练习。
通过多于一位小数来修复值总是正确舍入;例如,(1.0151).toFixed(2)按预期返回“1.02”。测试和polyfill都使用该知识进行正确性检查。
这意味着所有toFixed()实现都有一个简单的修复方法:如果值包含小数,则在要修改的值的字符串版本末尾附加“1”。这可能不是“符合规范的”,但这意味着我们将获得预期的结果,而无需重新审视较低级别的二进制或浮点运算。
在所有实现都被修改之前,如果您愿意这样做(并非每个人都愿意),您可以使用以下polyfill覆盖toFixed()。
<code>Object { given: "1.015", expected: "1.02", actual: "1.01" } Object { given: "4.015", expected: "4.02", actual: "4.01" } Object { given: "5.015", expected: "5.02", actual: "5.01" } Object { given: "6.015", expected: "6.02", actual: "6.01" } Object { given: "7.015", expected: "7.02", actual: "7.01" } Object { given: "128.015", expected: "128.02", actual: "128.01" }</code>
然后再次运行测试并检查结果长度是否为零。
<code class="language-javascript">(1.005).toFixed(2) == "1.01" || (function(prototype) { var toFixed = prototype.toFixed prototype.toFixed = function(fractionDigits) { var split = this.toString().split('.') var number = +(!split[1] ? split[0] : split.join('.') + '1') return toFixed.call(number, fractionDigits) } }(Number.prototype));</code>
或者只运行启动这篇文章的初始转换。
<code class="language-javascript">test({ fraction: .0015, maxValue: 516 }) // Array [] test({ fraction: .0015, maxValue: 516 }).length // 0</code>
感谢您的阅读!
JavaScript中的Number.toFixed()方法用于使用定点表示法格式化数字。它返回数字的字符串表示形式,该表示形式不使用指数表示法,并且小数点后恰好有指定数量的位数。如有必要,将对数字进行舍入,并且结果字符串的小数点后长度特定。
由于JavaScript处理二进制浮点数的方式,Number.toFixed()方法有时会给出不准确的结果。JavaScript使用二进制浮点数,它无法准确表示所有十进制小数。当使用Number.toFixed()方法将数字舍入到特定的小数位数时,这种不准确性会导致意外结果。
修复Number.toFixed()方法中的舍入错误的一种方法是使用自定义舍入函数。此函数可以考虑JavaScript的数字处理的特性并提供更准确的结果。例如,您可以使用一个函数,该函数将数字乘以10的幂,将其舍入到最接近的整数,然后将其除以相同的10的幂。
不可以,Number.toFixed()方法只能与数值一起使用。如果您尝试将其与非数值一起使用,JavaScript将抛出TypeError。如果您需要将此方法与可能不是数字的值一起使用,则应首先检查该值是否为数字。
Number.toFixed()方法与其他舍入方法之间的性能差异通常可以忽略不计。但是,如果您正在执行大量操作,则使用自定义舍入函数可能会比使用Number.toFixed()方法略快。
不可以,Number.toFixed()方法只能舍入到最多20位小数。如果您尝试舍入到超过20位小数,JavaScript将抛出RangeError。
Number.toFixed()方法处理负数的方式与处理正数的方式相同。它将数字的绝对值舍入到指定的小数位数,然后添加负号。
是的,Number.toFixed()方法是JavaScript的标准部分,应该在所有JavaScript环境中可用。但是,由于不同JavaScript引擎处理数字的方式不同,因此结果在所有环境中可能并不完全相同。
如果您不向Number.toFixed()方法传递任何参数,它将默认舍入到0位小数。这意味着它将把数字舍入到最接近的整数。
是的,您可以将Number.toFixed()方法与大数一起使用。但是,请记住,JavaScript只能准确表示最大为2^53 – 1的数字。如果您尝试将Number.toFixed()方法与大于此数字的数字一起使用,则它可能不会给出准确的结果。
以上是number()。tofixed()舍入错误:破裂但可固定的详细内容。更多信息请关注PHP中文网其他相关文章!