toFixed() 函数背后的规则是什么

What is the rule behind toFixed() function

我正在测试 javascript 的 toFixed() 方法。结果如下。

(49.175).toFixed(2) => "49.17"
(49.775).toFixed(2) => "49.77"
(49.185).toFixed(2) => "49.19"
(49.785).toFixed(2) => "49.78"

(49.1175).toFixed(3) => "49.117"
(49.1775).toFixed(3) => "49.178"
(49.1185).toFixed(3) => "49.118"
(49.1785).toFixed(3) => "49.178"

我在 chrome 浏览器上做了这个测试,我对结果感到惊讶。我无法理解其中的逻辑。它既不适合 'round away from zero' 也不适合 'round to even'。 'toFixed()'函数背后的规则是什么?

About toFixed

Returns 包含此数字值的字符串,以十进制定点表示法表示,小数点后有 fractionDigits 位。如果 fractionDigits 未定义,则假定为 0。具体执行以下步骤:

算法Number.prototype.toFixed (fractionDigits)https://www.ecma-international.org/ecma-262/5.1/#sec-15.7.4.5

  • toFixed方法的长度属性为1.

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

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

注意 对于某些值,toFixed 的输出可能比 toString 更精确,因为 toString 仅打印足够的有效数字来区分数字与相邻数值。

JS Work Around

function fix(n, p) {
  return (+(Math.round(+(n + 'e' + p)) + 'e' + -p)).toFixed(p);
}
let exampleA = fix(49.1175, 3);
let exampleB = fix(49.1775, 3);
let exampleC = fix(49.775, 2);
const random = Math.random();
console.log(exampleA);
console.log(exampleB);
console.log(exampleC);
console.log('Before:', random, 'After Custom =>', fix(random, 3), 'Default:', random.toFixed(3));
// 49.118
// 49.178
// 49.78

Precision Needed

我建议只是简单地将 set precisionC++ 移植到 Node.JS 模块。

  • 您可以简单地在 Node.JS 中安装并使用 child_process 来调用 C++ 程序带有一个参数,并让 C++ 运行 一个函数来转换值并输出到控制台。

来自MDN

toFixed() returns a string representation of numObj that does not use exponential notation and has exactly digits digits after the decimal place. The number is rounded if necessary, and the fractional part is padded with zeros if necessary so that it has the specified length. If numObj is greater or equal to 1e+21, this method simply calls Number.prototype.toString() and returns a string in exponential notation.

稍后您可以阅读:

WARNING: Floating point numbers cannot represent all decimals precisely in binary which can lead to unexpected results such as 0.1 + 0.2 === 0.3 returning false.

以上警告结合舍入逻辑(可能是数字的算术运算)将解释您在舍入过程中尝试的不同行为(您可以阅读 here)。

问题是,您输入的号码不存在!在扫描时,它们(二进制)四舍五入到最接近的 possible/existing 数字。 toPrecision(18) 更准确的显示扫描后的数字:

(49.175).toPrecision(18); // "49.1749999999999972" => "49.17"
(49.775).toPrecision(18); // "49.7749999999999986" => "49.77"
(49.185).toPrecision(18); // "49.1850000000000023" => "49.19"
(49.785).toPrecision(18); // "49.7849999999999966" => "49.78"

因此数字被四舍五入:首先是扫描,然后是 toFixed()