JavaScript 中的浮点数 (IEEE 754)

Floating point number in JavaScript (IEEE 754)

如果我没理解错的话,JavaScript 数字总是按照国际 IEEE 754 标准存储为双精度浮点数。这意味着它使用 52 位作为小数有效位。但是在上图中,二进制中的0.57似乎使用了54位。

另一件事是(如果我理解正确的话)二进制的 0.55 也是一个重复数。但是为什么0.55 + 1 = 1.55(没有损失)和0.57 + 1 = 1.5699999999999998

Number.prototype.toString 大致实现了 ES262 规范的以下部分:

7.1.12.1 NumberToString(m)

let n, k, and s be integers such that k ≥ 1, 10 ** k-1 ≤ s < 10 ** k,

the Number value for s × 10 ** n-k is m,

and k is as small as possible.

因此 toString 只是估计值,而不是 return 存储的确切字节数。

您在控制台中看到的也不是准确的表示。

toString(2) 打印字符串直到最后一个非零数字。

1.57 的位表示与 1 + 0.57 不同(但并非不可能得到结果 1.57),
但是二进制的 1 + 0.55 等于 1.55,如下面的代码片段所示:

console.log(1.57)
console.log(1.57.toString(2))
console.log((1+.57).toString(2))
console.log("1.32 + 0.25 = ",1.32 + .25)
console.log((1.32 + .25).toString(2))
console.log(1.55)
console.log(1.55.toString(2))
console.log((1+.55).toString(2))

请记住,计算机对二进制数执行运算,1.571.55 只是人类可读的输出

Which mean it uses 52 bits for fraction significand. But in the picture above, it seems like 0.57 in binary uses 54 bits.

JavaScript 的 Number 类型本质上是 IEEE 754 基本 64 位二进制浮点数,具有 53 位尾数。 52 位在“尾随有效数字”字段中编码。前导位通过指数字段编码(1-2046 的指数字段表示前导位为 1,0 的指数字段表示前导位为零,2047 的指数字段用于无穷大或 NaN)。

您看到的 .57 值有 53 个有效位。领先的“0”。由 toString 操作产生;它不是数字编码的一部分。

But why 0.55 + 1 = 1.55 (no loss) and 0.57 + 1 = 1.5699999999999998.

当 JavaScript 正在格式化一些 Number x 以使用其默认规则显示时,这些规则说要生成最短的十进制数字(在其显着数字,不包括像前导“0.”这样的装饰),当转换回 Number 格式时,会产生 x。此规则的目的包括 (a) 始终确保显示唯一地标识哪个确切的 Number 值是源值,以及 (b) 使用的数字不超过实现 (a) 所需的数字。

因此,如果您以 .57 等十进制数字开头并将其转换为 Number,您将得到一些值 x,这是转换必须四舍五入为 Number 格式的数字。然后,当 x 格式化显示时,您会得到原始数字,因为生成最短数字的规则会转换回 x自然生成您开始使用的数字。

(但是 x 并不 完全 表示 0.57。最接近 0.57 的 double 略低于它;见the decimal and binary64 representations of it on an IEEE double calculator).

另一方面,当您执行诸如 .57 + 1 之类的运算时,您正在执行一些算术运算,生成的数字 y 不是简单的十进制数字。因此,在格式化此类数字以供显示时,规则可能需要使用更多数字。换一种说法。当您添加 .571 时,Number 格式的结果与您从 1.57 获得的数字不同。因此,要格式化 .57 + 1 的结果,JavaScript 必须使用更多数字来将该数字与您从 1.57 获得的数字区分开来——它们不同,必须以不同方式显示。


如果 0.57 可以精确表示为 double,则总和的预舍入结果将正好是 1.57,因此 1 + 0.57 将舍入为与 [= 相同的 double 23=].

但事实并非如此,实际上是1 + nearest_double(0.57) =
1.569999999999999951150186916493(预舍入,而不是 double)四舍五入为
1.56999999999999984012788445398。这些数字的十进制表示比我们需要区分尾数的 1ulp(单位在最后一位)或什至 0.5 ulp 最大舍入误差所需的位数多得多。

1.57 舍入为 ~1.57000000000000006217248937901,因此这不是打印 1 + 0.57 结果的选项。十进制字符串需要将数字与相邻的 binary64 值区分开来。


碰巧发生在 .55 + 1 中的舍入产生与将 1.55 转换为 Number 相同的数字,因此显示 .55 + 1 的结果产生“1.55”。