04 浮点数:0.1 + 0.2 的位级真相¶
这一章讲什么¶
每个学 JavaScript 的人都见过这个笑话:0.1 + 0.2 === 0.3 返回 false,
加起来是 0.300000000000000041。网上把它当「JS 之怪」传,但原书第一句话就说:
这不是 bug,是 JavaScript 把所有数字都存成一种格式的直接后果2。
那个格式叫 IEEE 754 双精度浮点数,理解了它,你不但能解释这个笑话,
还能预判一类生产事故:对账差一分钱、ID 对不上、某次计算莫名 NaN。
本章(连同下一章)走完整条链:数字怎么变成字节(二进制、补码)→ 为什么整数不够用(浮点是什么)→ 64 个二进制位怎么分工(符号、指数(2 的多少次方)、尾数)→ 0.1+0.2 的逐位推演 → 这个格式的能力边界(EPSILON、NaN、安全整数)→ 逃出浮点的三条路(BigInt、Decimal、V8 的 SMI 快道,下一章讲)。
顶层全景:一个数字的 64 位工位¶
┌───┬───────────────┬──────────────────────────────────────────────────┐
│ 1 │ 11 │ 52 │
│位 │ 位 │ 位 │
│符号│ 指数 │ 尾数(有效数字) │
│ ± │ 2 的多少次方 │ 1.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx │
└───┴───────────────┴──────────────────────────────────────────────────┘
值的正负 小数点漂在哪 实际的有效数字(53 位,首位的 1 白送)
图说:64 位里没有「整数区」「小数区」,只有「符号 × 1.尾数 × 2^指数」;
所以 0.5 和 500000000 占用完全相同的 64 位,精度却天差地别。
核心原理¶
3.1 先修地基:二进制与补码¶
二进制就是只拿 0 和 1 两个数字来计数,每个位置代表 2 的幂(十进制每个位置是 10 的幂)。
1011 = 1×2³ + 0×2² + 1×2¹ + 1×2⁰ = 8+2+1 = 113。8 个二进制位(也就是 1 字节)
能表示 2⁸ = 256 个值,因为 0 也要占一个位置,所以无符号范围是 0–2554。
这套「普通二进制」只能表示非负整数。
要表示负数,工业界的主流方案叫补码(2's complement):把最高位指定为符号位,
而负数的编码 = 正数的所有位取反、再加 15。它看起来古怪,但有一个绝妙的性质:
加一个数和加它的负数,用同一套电路就能得到 0(最高位的进位直接丢掉)。
书里拿 -1 做了演示:11111111 + 00000001 = 1 00000000,第 9 位溢出丢弃,
剩下全零6。补码不是谁拍脑袋的审美选择,是让加减法不用分情况讨论的工程答案。
补码还有个近亲叫偏置编码(biased encoding):存的时候先加一个固定偏置
(8 位就用 128),-128 存成 0,1 存成 129。它比较大小方便,但加减法要额外处理偏置,
所以只用在特殊场合7——浮点的指数区正是用的它,马上见到。
3.2 浮点是什么:科学计数法的二进制版¶
主线例子:把 123.45 存进 64 位。 先建立直觉:浮点(floating point)就是科学计数法, 只不过进制从 10 换成 2。十进制里 123.45 = 12345 × 10⁻²;浮点里任何一个数都拆成三件套8:
- 符号:一个 bit,0 正 1 负;
- 尾数(significand,也有人叫 mantissa):1.xxx 那个有效数字部分;
- 指数:2 的多少次方——小数点「漂」到哪由它决定,这就是「浮点」名字的由来9。
64 位的分工原书给得很清楚:1 位符号、11 位指数、52 位尾数10。
指数区用偏置编码,偏置是 1023:真实的指数 -2 要存成 -2+1023 = 1021
(01111111101)11。为什么需要偏置?因为指数有正有负,而 11 位二进制本身存的是
无符号数——加 1023 这个偏移量,就把「-1022 到 +1023」搬进了「1 到 2046」,
比较和硬件处理都省事(这也是 3.1 节说补码之外还有偏置这条路的用处)。
现在解码 123.45,这是全书最值钱的 walked example:
64 位 = 0 | 10000000101 | 1110110111001100110011001100110011001100110011001101
① 符号位 0 → 正数
② 指数区 10000000101 = 1029;1029 − 1023(偏置) = 6 → 乘 2^6 = 64
③ 尾数区解码 = 0.92890625;加上白送的首位 1 → 1.92890625
④ 合起来:1.92890625 × 64 = 123.45 ✓
(这四个数字全部来自原书的逐步解码,可直接复算12。) 注意第 ③ 步的「白送的首位 1」:规格化二进制科学计数法里,任何数都是 1. something × 2ⁿ, 小数点前的 1 必然存在——既然必然存在,存它就是浪费。IEEE 754 的设计者把这个 1 扔掉, 用 52 位的存储换到了 53 位的实际精度13。
3.3 0.1 + 0.2:逐位推演,误差(结果跟精确值的偏差)在哪一步产生¶
现在回答本章开头的问题。推演分三步,每步都有具体的位14:
第 ① 步:0.1 和 0.2 根本存不准。 把它们各自规格化成二进制:
像 1/3 在十进制里是 0.333… 一样,0.1 和 0.2 在二进制里都是无限循环小数;
而尾数只有 52 位,装不下无限——在相加之前,它们就已经各自被四舍五入了一次15。
还有个细节值得记住:0.2 明明是 0.1 的两倍,指数却不同(一个 -4 一个 -3),
因为它们分处 2 的幂的两侧16。
第 ② 步:对齐(把两个数的小数点摆到同一位置)指数再相加。 加法要求指数一致, 0.2 的指数大 1,于是 0.1 的尾数整体右移一位,最高位的有效数字被挤出去17:
第 ③ 步:再规格化、再舍入,误差第二次进场。 和的小数点左边变成了 10,
要重新规格化成 1.xxx × 2ⁿ,指数加 1;而结果有 54 位,尾数只有 52 位——
多出来的尾巴再次四舍五入,进位顺着 0011 的循环一路传播18。
最终存入的是 1.00110011…0100 × 2^-2,而真正的 0.3 应该是 1.00110011…0011 × 2^-2——
最后几位不同,换算回十进制,差就是 0.0000000000000000419。
结论一句话:这不是 JavaScript 的怪癖,是「十进制小数放进二进制容器」的数学宿命; Python、Java、C++,凡用 IEEE 754 的语言,结果一字不差20。
3.4 能力边界:EPSILON、NaN、和安全整数天花板¶
比「差不多相等」:Number.EPSILON。 既然浮点误差不可避免,
a === b 经常不是你要的。语言给了一个常量 Number.EPSILON,
值是 2⁻⁵² ≈ 2.22×10⁻¹⁶——两个可表示的数之间能有的最小差距21。
所以判等改成「差得够不够小」:Math.abs(a - b) < Number.EPSILON。
但书里立刻警告:EPSILON 必须按数的量级缩放——比较 0.1 和 0.3 时
2.22×10⁻¹⁶ 够用,比较两个亿级大数时,可表示的间距远大于这个值,
直接用会误判。正确写法是让 epsilon 乘上两个数里较大的绝对值22。
NaN 其实是 9 千万亿个值。 指数全 1、尾数非 0 的编码全部判为 NaN(Not a Number,
「不是数」)——这样的编码在 Binary64 里有 2⁵³−2 = 9,007,199,254,740,990 个,
而 JavaScript 把它们全部显示成同一个 NaN23。无穷大则是指数全 1、尾数全 0,
分正负两个24。整个格式的动态范围惊人:最小非零 5×10⁻³²⁴,最大 1.8×10³⁰⁸,
但代价是全范围只有约 2⁶⁴−2⁵²+3 个可表示的值,而且数越大,相邻两个可表示数之间的
缝隙越大25——这正是下一节的灾难根源。
安全整数天花板:2⁵³−1。 尾数有 53 位精度,意味着「能精确表示的整数」到 2⁵³−1 = 9,007,199,254,740,991 为止。超过它,可表示的数开始跳号:
console.log(9007199254740992); // 2^53 → 9007199254740992 ✓
console.log(9007199254740993); // 2^53 + 1 → 9007199254740992 ✗ 被吞了!
console.log(9007199254740994); // 2^53 + 2 → 9007199254740994 ✓
(三个数与结果均来自原书实测26。)9,007,199,254,740,993 这个数字 在 JavaScript 里字面意义上不存在——引擎会把它存成相邻的偶数27。 没有报错,没有警告,就是个看起来无害的错数。
和数据库的冲突。 很多数据库用 uint64(无符号 64 位整数)做主键, 上限 18,446,744,073,709,551,615——比 JavaScript 的安全整数上限大两千多倍28。 后果是个经典生产事故:后端把一个 uint64 ID 序列化(把内存里的值转成可传输的文本)成 JSON(一种通用的文本数据格式)数字传给前端, JavaScript 一解析就静默改号。书里的对策很现实:超过安全整数的值, 要么按字符串传,要么用 BigInt(下一章)29。
3.5 精度问题什么时候真的要命¶
浮点误差大多数时候无所谓:动画位移差 10⁻¹⁶ 像素,没人看得见。
书里把话说得很直白:要命的是钱。货币计算里,每次加减乘除丢的那一点点,
聚合起来就是真金白银的对不上账30。19.99 美元的商品按 8.25% 计税,
19.99 * 0.0825 得到 1.6491749999999998 而不是 1.64917531——
发票上的每一位都是用户能看见的。
这一节不给「银行家舍入」之类的完整方案(完整的答案在下一章的 BigInt/Decimal),
只立一条判别标准:这个数的误差有没有人(或另一个系统)会当真?会,就别用浮点。
3.1b 补码的完整对照:为什么 −1 是全 1¶
把 8 位补码的前几个值列全,规律就一目了然35:
| 符号位 | 其余 7 位 | 十进制值 |
|---|---|---|
| 0 | 0000000 | 0 |
| 0 | 0000001 | 1 |
| 0 | 0000010 | 2 |
| 0 | 0000011 | 3 |
| 1 | 1111111 | −1 |
| 1 | 1111110 | −2 |
| 1 | 1111101 | −3 |
正数按普通二进制数;负数是「取反加一」的结果,而且单调性保持: 1111111(−1)> 1111110(−2),二进制的大小比较和十进制值的大小比较一致—— 这正是偏置编码做不到、补码能独占鳌头的原因36。
书里最直观的一段演示是把 −1 和 1 加起来,逐位列出进位37:
位: 7 6 5 4 3 2 1 0
-1: 1 1 1 1 1 1 1 1
1: 0 0 0 0 0 0 0 1
进位: 1 1 1 1 1 1 1
结果: 0 0 0 0 0 0 0 0 ← 第 9 位进位溢出,直接丢弃
从右往左:第 0 位 1+1=10,写 0 进 1;接下来每一位都是 1+0+进位 1=10, 写 0 进 1;最后的进位落到第 8 位,超出了 8 个位的容量,丢弃—— 剩下全是 0。这套「溢出即丢弃」的算术电路,让带符号数和无符号数 共用同一套硬件加法器,是补码被选中的根本理由38。
3.1c 字节序:一个数拆成多个字节时,谁先谁后¶
数字一大就要跨字节。书里给的例子:34,375 的 16 位二进制拆成两个字节是
0x86(高位)和 0x47(低位),「谁先摆上桌面」有两种约定39:
- 大端(big-endian,网络字节序):高位在前,写作
86 47—— 像我们从左到右写数字,好读; - 小端(little-endian):低位在前,写作
47 86—— 像把 34,375 读成「5 个一、7 个十、3 个百……」倒着来。
大多数现代系统内部用小端(硬件优化方便),网络传输统一用大端 (约定好,人人可解)40。书里为清晰起见示例统一用大端; 而我们日常几乎不碰字节序——这一节的意义在于: 「一个数」和「它在内存/线路上的字节」之间,隔着一层约定。 IEEE 754 讲的正是这层约定在 64 位上的精确版本。
顺带记住书里的一句提醒:光给一串位(比如 11111111)而不说编码方式,
你根本无法知道它表示 255、−1 还是别的什么——位不携带含义,编码才携带41。
3.2b 为什么是「浮点」:固定点做不到的事¶
「浮点」这个名字说的是小数点的位置不固定,随指数漂移。
对比之下,定点(fixed-point)表示法把小数点钉死:比如约定 8 位整数 + 4 位小数,
就只能表示 12345678.1234 这种形态——整数部分一大,小数部分就被挤没,
反过来也一样42。浮点用「1.尾数 × 2^指数」的结构,
让 0.000000000000123 和 123,000,000,000,000 用同一套 64 位装下,
代价是有效数字永远只有 53 位:数越大,绝对精度越差43。
123.45 那组解码数字(1.92890625 × 64)就是这个漂移的实证:
指数 +6 把小数点右推了 6 位。
3.2c 尾数逐位解码:52 位怎么变成 53 位精度¶
隐含的 leading 1 白送之后,剩下的 52 位每一位代表 2 的负幂: 第 1 位是 2⁻¹(0.5),第 2 位是 2⁻²(0.25),第 3 位是 2⁻³(0.125)…… 一直到第 52 位的 2⁻⁵²44。书里给了一个可复算的样例: 如果 52 位按「010101…」交替置位,总和是
再乘上指数给出的 2 的幂和符号,就是最终数值45。 「52 位存储、53 位精度」的技巧全部家当就是这句: 规格化二进制数小数点前必是 1,所以不用存46。
3.3b 0.1 + 0.2 的加法全程:三步之后,错在哪个比特¶
3.3 节给了结论,这里把位级的每一步走完,全部数字可在原书表 3.3–3.5 里核对47:
起点: 0.1 与 0.2 存入后的尾数都是 1.1001100…(0011 无限循环),
0.1 的指数是 01111111011,0.2 的是 01111111100——后者恰好大 1。
注意两个尾数看起来相同而指数不同,因为 0.2 跨过了 2 的幂边界:
0.1 ≈ 1.6×2⁻⁴,0.2 ≈ 1.6×2⁻³,就像十进制里 0.5 和 5 的指数不同48。
移位对齐: 把 0.1 的尾数右移一位、指数加 1,两者对齐:
0.1 变成 0.1100110…0,0.2 保持 1.1001100…49。
右移挤掉的那一位,是进入加法前的第三次信息损失(存入时各舍入过一次)。
逐位相加: 从右往左做二进制加法,进位沿 0011 循环一路传导,
得到 10.0110011…110——小数点左边出现 10,不再规格化50。
再规格化 + 再舍入: 小数点右归一位、指数加 1;结果是 54 位,
尾数只留 52 位,末尾的 10 被四舍五入成 11 还是 00,
取决于第 53 位——进位再一次顺循环传导51。
最终落盘的 1.00110011…0100 × 2⁻² 与真值 0.3 的
1.00110011…0011 × 2⁻² 差在最后几位,十进制里就是
0.30000000000000004 与 0.3 的距离52。
全链条一共四处损失:两次存入舍入、一次对齐移位、一次结果舍入——
没有一处是「加法算错」,全是「容器装不下」53。
3.4b Infinity、负无穷与 NaN 的位模式¶
指数全 1、尾数全 0 的两个位模式分给正负无穷:
指数全 1、尾数非 0 的,全部归 NaN54。1/0 得 +∞,
Number('abc') 得 NaN,Infinity - Infinity 也得 NaN——
「无穷减无穷没有定义」这条数学规则,在二进制里就是「落入 NaN 编码区」。
而 Binary64 全境的可用值只有约 2⁶⁴−2⁵²+3 个,
摊到 ±1.8×10³⁰⁸ 的范围上,大数附近的「间距」大得惊人:
在 2⁵³ 附近,相邻两个可表示数之间隔 2;到 2⁶⁰,间隔变成 12855。
「数越大,加一越可能什么都没发生」就是这个几何的直观。
3.4c Math 对象与它的脾气¶
Math 不是类型,是常量与函数的集合处(书里叫它 namespace):
Math.PI、Math.E;round/floor/ceil/abs/pow/sqrt,
三角函数用弧度,Math.random() 返回 0 到 1 之间的伪随机数56。
两个脾气要记:所有 Math 方法只吃 Number,
喂不进去的输入返回 NaN 而不是抛错——「静默失败」的浮点版;
以及 Math.round 的「逢半向上」不是银行家舍入,
钱的场景别指望它57。
3.4d 为什么是 2 的 53 次方:安全整数的二进制解释¶
「最大安全整数是 2⁵³−1」是个容易被当成咒语背的数字,它的来历其实一句话: 尾数区 52 位,加上白送的 leading 1,一共 53 位有效二进制数字; 53 位恰好能无歧义地覆盖的所有整数,到 2⁵³−1 为止58。 推导走一遍:一个规格化二进制数能精确表示的整数,其位数不能超过有效数字的位数; 53 位有效数字 ⇒ 最多精确表示 53 位的整数;53 位整数的最大值是 2⁵³−1 (52 位元后接 52 个 0 再减 1 的形态,换成十进制就是 9,007,199,254,740,991)。 超过它,相邻可表示数之间的距离变成 2,奇数开始「被四舍五入到隔壁」, 2⁵³+1 就此消失59。安全整数的「安全」两个字说的就是这个: 在它以内,每个整数都有专属的位模式,一一对应,谁也不挤谁。
这也解释了为什么 EPSILON 是 2⁻⁵²:1 和「比 1 大的下一个可表示数」之间, 差的正是尾数最后一位的一步——2⁻⁵²60。同一个 52/53 的分界线, 一头是「最大的整数」,一头是「最小的差」,同一个机制的两张脸。
3.4e 浮点误差的「剂量」问题¶
原书在「Limitations of floating-point」一节把话说得很克制: 浮点运算——按 64 位二进制小数做的一切计算——「只能逼近正确值」,这个损失「叫精度丢失(loss of precision)」; 对大多数场景无所谓,但钱会把它放大——每次加减乘除丢一点, 聚合成账面上的实损61。判断标准书里其实已经给了,只是我们把它提亮: 这个数的误差有没有人(或另一个系统)会当真? 没人当真(动画帧位移、科学计算的中间量),浮点随便用; 有人当真(价格、余额、数量),立刻换 3.5 节的方案62。 「不谈剂量谈毒性」在这里同样成立:0.1+0.2 的误差剂量是 4×10⁻¹⁷, 在粒子物理里是灾难,在购物车总额里 invisible——直到它撞上 「展示给用户的每一位」这条边界63。
3.0b 章首的三个坐标:这一章在原书里的位置¶
原书第 3 章开篇把全章的地理交代得很清楚,这里照录坐标64:
- 语言只给两种数字类型:Number 和 BigInt——别的语言让你选整数/小数、 有符号/无符号、8 位/64 位,JavaScript 一概不让你选65;
- 一个决定,一串后果:
0.1+0.2≠0.3、金融计算差一分钱、 数过 9,007,199,254,740,991 不再可靠、V8 偷偷把小整数区别对待—— 全是「所有数字都是双精度浮点」这一个决定的 downstream66; - 全章走三个角度:IEEE 754 怎么定义浮点、BigInt 怎么当逃生舱、 V8 怎么给小整数开快道;外加一个预告——正在提案的 Decimal 「可能终于让 JavaScript 能精确计算 0.1+0.2」67。
记住这套坐标,读后面任何一段都能回答「我现在在全章的哪一块」。
3.1e 与第 2 章的隐秘呼应:数字也是一种编码¶
读完本章回头看,第 2 章的那套语言在第 3 章全部重演了一遍, 这个对称不是巧合,是原书的有意安排68:
- 字符串是「16 位整数的序列」,数字是「64 位的特定布局」—— 两者都是「位 → 含义」的编码问题,只是字符编码管符号,数值编码管量;
- 字符串有 UTF-32/16/8 三种容器格式,数字有 Binary64/Decimal128/BigInt 三种容器——都在「定宽简单」与「变宽通用」之间做权衡;
- 字符串事故来自「编码假设错位」(Shift-JIS 当 UTF-8), 数字事故来自「精度假设错位」(uint64 ID 当 Number)—— 都是跨系统边界时,两边对「容器」的理解不一致。
所以这本书教的不是两个孤立的知识点,而是一双眼睛: 看到任何「值」,都问一句「它在容器里怎么躺着的、跨边界时对方怎么解读的」。 第 2 章的字符串、第 3 章的数字、第 5 章的对象(隐藏类), 是同一双眼睛的三次使用。
3.3c 把 0.1+0.2 讲给别人听:一分钟版本¶
合上这一章,试着用这个顺序复述 0.1+0.2,能讲下来才算真懂69:
- 0.1 和 0.2 写成二进制都是无限循环(像 1/3 的 0.333…), 64 位装不下 → 存入时就各自舍入了一次;
- 相加前要对齐指数(像十进制对齐小数点), 0.1 的尾数右移,又损失一位;
- 加完是 54 位,尾数只有 52 位,再舍入一次, 进位顺着循环传导,末几位变了;
- 落盘的位串换算回十进制,就是 0.30000000000000004;
- 这不是 bug:IEEE 754 的每种语言、每个实现,结果一字不差。
五步,四处舍入,零处「算错」。浮点的全部故事,就是「装不下」三个字。
讲给别人听时最常卡住的追问有两个,提前备好答案。 问:「为什么不干脆用十进制存?」——答:这就是 Decimal128 在做的事 (见 05 章 3.2 节),代价是更宽的容器和更慢的运算; 问:「为什么别的语言没这毛病?」——答:都有,Python、Java、C++ 结果一字不差,只是有的语言默认容器不是浮点、或默认显示位数把它藏住了70。
作者的判断与证据¶
有证据的判断:
- 123.45 的逐步解码、0.1/0.2 的指数与循环模式、三步加法推演,全部给出具体位串, 可手算复核32;「所有 IEEE 754 语言结果一致」是可实验验证的论断。
- 2⁵³ 附近三个整数的输出是实测结果33。
作者的主张:
- 「钱必须用别的方案算」是立场鲜明的实践主张,书里给出的选项(下一章展开) 本身也是「还在演进中」的方案(如 Decimal 尚未定型)。
- 「Number.EPSILON 是最被忽视的特性之一」属作者评价。
判断(我们的,不是书里的): 用 0.1+0.2 一个例子贯穿整章的教法非常划算—— 它同时教会读者「浮点是相对精度」「误差来自两次舍入」「比较要按量级缩放容差」 三件事;相比之下,直接背「浮点数有误差」的教训几乎留不住。 如果错,会错在: 如果读者只记住「0.1+0.2 会出错」而没记住「按量级比较」, 这一章就退化成又一个梗图;教法的效果依赖读者动手复算,而不是读完点头。
边界与局限¶
- 本章只覆盖了 IEEE 754 的「够用」部分;舍入模式、次正规数(subnormal)、 异常标志这些规范内容原书明说「 irrelevant 于 JavaScript」而跳过34。
- 「大多数语言结果一致」有个历史脚注:某些旧语言/编译器在中间计算用 更高精度(扩展双精度),结果可能不同——原书未提,属实现史细节。
- 安全整数的处理方式(转字符串)是权宜之计;JSON 规范本身没有 BigInt 类型, 跨系统传大整数至今仍是个协议(双方约定好的交换格式)设计问题。
可带走的¶
- Number = IEEE 754 双精度浮点:1 符号 + 11 指数(偏置 1023)+ 52 尾数(实际 53 位)。
- 0.1+0.2≠0.3 的完整链条:无限循环二进制 → 存入前舍入 → 对齐指数移位 → 结果再舍入。
- 判定浮点相等:差值 < Number.EPSILON × max(|a|,|b|),永远不要裸用 EPSILON。
- NaN 有 9,007,199,254,740,990 个编码,显示成一个;Infinity 有两个编码。
- 安全整数上限 2⁵³−1 = 9,007,199,254,740,991;uint64 ID 必须按字符串或 BigInt 传。
- 规格化二进制的小数点前必是 1,所以 52 位存储换 53 位精度。
- 数越大缝隙越大:大数上的「加 1」可能什么都不发生。
- 钱的计算别赌浮点;判别标准是「误差会不会有人当真」。
原文地图¶
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 0.1+0.2 与「不是 bug」 | 3 Numbers | text/13-ch03-3-numbers.txt:51(搜「0.30000000000000004」) · :59(搜「double-precision floating-point values」) |
| Number 的规范定义 | 同上 | text/13-ch03-3-numbers.txt:77(搜「conforming to IEEE 754」) |
| IEEE 754 标准与高价下载 | 同上 | text/13-ch03-3-numbers.txt:90(搜「over a hundred US dollars」) |
| 二进制与 0–255 | 同上 | text/13-ch03-3-numbers.txt:189(搜「256 possible numeric values」) |
| 补码:取反加一 | 同上 | text/13-ch03-3-numbers.txt:195(搜「sign bit」) · :201(搜「complement), and add 1」) |
| 浮点三件套 | 同上 | text/13-ch03-3-numbers.txt:565(搜「scientific notation」) |
| 64 位分工与偏置 1023 | 同上 | text/13-ch03-3-numbers.txt:589(搜「eleven exponent bits」) · :601(搜「bias of 1023」) |
| 53 位精度的技巧 | 同上 | text/13-ch03-3-numbers.txt:641(搜「53 bits of precision」) |
| 123.45 逐步解码 | 同上 | text/13-ch03-3-numbers.txt:704(搜「decode the number 123.45」) · :743(搜「1.92890625」) |
| 0.1/0.2 的循环模式 | 同上 | text/13-ch03-3-numbers.txt:828(搜「repeating pattern 1001」) |
| 对齐指数 | 同上 | text/13-ch03-3-numbers.txt:834(搜「shift 0.1's significand」) |
| 「每个语言都一样」 | 同上 | text/13-ch03-3-numbers.txt:879(搜「Every language using IEEE 754」) |
| Number.EPSILON 与量级缩放 | 同上 | text/13-ch03-3-numbers.txt:1007(搜「smallest difference」) · :1029(搜「scaled based on the magnitude」) |
| NaN 的 9,007,199,254,740,990 个值 | 同上 | text/13-ch03-3-numbers.txt:1085(搜「9,007,199,254,740,990」) |
| 5e-324 到 1.8e308、缝隙变大 | 同上 | text/13-ch03-3-numbers.txt:1101(搜「5e-324」) |
| 最大安全整数 | 同上 | text/13-ch03-3-numbers.txt:1107(搜「maximum safe integer」) |
| 9,007,199,254,099,3 不存在 | 同上 | text/13-ch03-3-numbers.txt:1132(搜「literally cannot exist」) |
| uint64 主键冲突与字符串方案 | 同上 | text/13-ch03-3-numbers.txt:1138(搜「2,000 times larger」) |
| 货币误差聚合 | 同上 | text/13-ch03-3-numbers.txt:1052(搜「lose precision」) |
-
出处:「3 Numbers」第 51 段(
text/13-ch03-3-numbers.txt:51,搜「0.30000000000000004」)。三个console.log的输出:和为0.30000000000000004,字面量0.3显示为0.3,严格相等为false。 ↩ -
出处:「3 Numbers」第 59 段(
text/13-ch03-3-numbers.txt:59,搜「direct consequence」):「It's not a bug. It's a direct consequence of how JavaScript chose to represent all numbers as double-precision floating-point values.」 ↩ -
出处:「3 Numbers」第 154 段(
text/13-ch03-3-numbers.txt:154,搜「Binary: 1011」)。「每个位置代表 2 的幂」在同文件第 140 段(搜「powers of 2」)。 ↩ -
出处:「3 Numbers」第 189 段(
text/13-ch03-3-numbers.txt:189,搜「256 possible numeric values」)。「regular binary layout(普通二进制布局)」这个术语在同段。 ↩ -
出处:「3 Numbers」第 195 段与第 195 段(
text/13-ch03-3-numbers.txt:195,搜「sign bit」;:201搜「invert all its bits」)。「2's complement」一名出自同段。 ↩ -
出处:「3 Numbers」第 298–445 段(
text/13-ch03-3-numbers.txt:298,搜「Table 3.2」);「11111111 + 00000001 = 0(丢弃进位)」的逐位列在同文件第 428–440 段(搜「overflows beyond 8 bits」)。 ↩ -
出处:「3 Numbers」第 458 段(
text/13-ch03-3-numbers.txt:458,搜「biased encoding」)。「比较大小方便、算术不自然」的权衡在同段。 ↩ -
出处:「3 Numbers」第 565 段(
text/13-ch03-3-numbers.txt:565,搜「scientific notation」)。 ↩ -
出处:「3 Numbers」第 577–583 段(
text/13-ch03-3-numbers.txt:577,搜「sign bit」;:583搜「significand」)。「尾数也叫 mantissa,两词通用」见第 583 段。 ↩ -
出处:「3 Numbers」第 589 段(
text/13-ch03-3-numbers.txt:589,搜「eleven exponent bits」)。「Binary64 / double-precision」的名目见第 595 段(搜「double-precision」)。 ↩ -
出处:「3 Numbers」第 601 段(
text/13-ch03-3-numbers.txt:601,搜「bias of 1023」)。「-2 存成 1021」的算例在同段。 ↩ -
出处:「3 Numbers」第 704–746 段(
text/13-ch03-3-numbers.txt:711,搜「10000000101」;:740搜「26 = 64」;:743搜「1.92890625」;:746搜「123.45」)。 ↩ -
出处:「3 Numbers」第 641 段(
text/13-ch03-3-numbers.txt:641,搜「53 bits of precision while only using 52 bits」)。「规格化数必是 1.something」见第 647 段(搜「leading digit」)。 ↩ -
出处:「3 Numbers」第 775–996 段(
text/13-ch03-3-numbers.txt:775,搜「Table 3.3」)。推演三步分别对应:存入前舍入(第 828 段)、对齐指数(第 834–864 段)、结果再舍入(第 873 段与第 955–995 段)。 ↩ -
出处:「3 Numbers」第 828 段(
text/13-ch03-3-numbers.txt:828,搜「forced to round them to fit in 52 bits」)。 ↩ -
出处:「3 Numbers」第 822 段(
text/13-ch03-3-numbers.txt:822,搜「crosses a power-of-two boundary」)。 ↩ -
出处:「3 Numbers」第 834 段(
text/13-ch03-3-numbers.txt:834,搜「shift 0.1's significand right by one」)。移位后的两行二进制见表 3.4(第 840 段起)。 ↩ -
出处:「3 Numbers」第 873 段与第 961–995 段(
text/13-ch03-3-numbers.txt:873,搜「accumulating more tiny errors」;:961搜「Normalize」;:964搜「Round」)。 ↩ -
出处:「3 Numbers」第 976–995 段(
text/13-ch03-3-numbers.txt:976,搜「1.0011001100110011001100110011001100110011001100110100」;:989搜「1.0011001100110011001100110011001100110011001100110011」)。两者之差换算成十进制即 0.30000000000000004 与 0.3 的距离,见第 995 段(搜「last handful of bits」)。 ↩ -
出处:「3 Numbers」第 879 段(
text/13-ch03-3-numbers.txt:879,搜「Every language using IEEE 754」)。 ↩ -
出处:「3 Numbers」第 1007 段(
text/13-ch03-3-numbers.txt:1007,搜「smallest difference between two representable numbers」)。almostEqual 的用法示例在同文件第 1020–1023 段。 ↩ -
出处:「3 Numbers」第 1029 段与等比缩放版实现(
text/13-ch03-3-numbers.txt:1029,搜「scaled based on the magnitude」;实现在第 1036–1040 段,搜「equalish」)。 ↩ -
出处:「3 Numbers」第 1085 段(
text/13-ch03-3-numbers.txt:1085,搜「9,007,199,254,740,990」)。「全显示成同一个 NaN」在同段。 ↩ -
出处:「3 Numbers」第 1071–1080 段(
text/13-ch03-3-numbers.txt:1077,搜「01111111 11110000」)。两个无穷大的位模式分别见第 1077 与第 1080 段。 ↩ -
出处:「3 Numbers」第 1101 段(
text/13-ch03-3-numbers.txt:1101,搜「gaps between representable numbers」)。 ↩ -
出处:「3 Numbers」第 1114–1122 段(
text/13-ch03-3-numbers.txt:1114,搜「9007199254740992」)。 ↩ -
出处:「3 Numbers」第 1132 段(
text/13-ch03-3-numbers.txt:1132,搜「literally cannot exist」)。原话即如此,还带着感叹号。 ↩ -
出处:「3 Numbers」第 1138 段(
text/13-ch03-3-numbers.txt:1138,搜「18,446,744,073,709,551,615」)。「大两千多倍」在同段(搜「over 2,000 times larger」)。 ↩ -
出处:「3 Numbers」第 1138 段(
text/13-ch03-3-numbers.txt:1138,搜「stored as strings」)。 ↩ -
出处:「3 Numbers」第 1052 段(
text/13-ch03-3-numbers.txt:1052,搜「quite large sums of money」)。 ↩ -
出处:「3 Numbers」第 1627–1631 段(
text/13-ch03-3-numbers.txt:1627,搜「19.99」;:1631搜「1.6491749999999998」)。 ↩ -
出处:「3 Numbers」第 711–746 段与第 796–995 段(
text/13-ch03-3-numbers.txt:802,搜「1.1001100110011」)。 ↩ -
出处:「3 Numbers」第 1114–1122 段(
text/13-ch03-3-numbers.txt:1114,搜「console.log(9007199254740992)」)。 ↩ -
出处:「3 Numbers」第 122 段(
text/13-ch03-3-numbers.txt:122,搜「not going to cover the full detail」)与第 633 段(搜「deep details of IEEE-754」)。 ↩ -
出处:「3 Numbers」表 3.1(
text/13-ch03-3-numbers.txt:207,搜「2’s complement Encoding」)与第 189–195 段。 ↩ -
出处:「3 Numbers」第 458–464 段(
text/13-ch03-3-numbers.txt:458,搜「biased encoding」):偏置方案「比较大小方便,但算术不自然」。 ↩ -
出处:「3 Numbers」表 3.2(
text/13-ch03-3-numbers.txt:304,搜「Binary addition showing why -1」)与第 428–440 段的逐步进位说明。 ↩ -
出处:「3 Numbers」第 445 段(
text/13-ch03-3-numbers.txt:445,搜「without special cases」):「同样的算术处理正负数,不用分情况、不加额外复杂度。」 ↩ -
出处:「3 Numbers」第 491–500 段(
text/13-ch03-3-numbers.txt:491,搜「0x86」):34,375 拆成 0x86 与 0x47 的例子。 ↩ -
出处:「3 Numbers」第 505 段(
text/13-ch03-3-numbers.txt:505,搜「little-endian is easier」):「大多数现代系统内部小端、网络传输大端,因为小端对硬件优化更友好。」 ↩ -
出处:「3 Numbers」第 545–552 段(
text/13-ch03-3-numbers.txt:551,搜「impossible to know」):只看位序列不知道最高位是不是符号位,解读需要额外的编码信息。 ↩ -
出处:「3 Numbers」第 698 段(
text/13-ch03-3-numbers.txt:698,搜「fixed-point」):定点表示「留 8 位整数 4 位小数,就只能是 12345678.1234 这种形态」。 ↩ -
出处:「3 Numbers」第 757 段(
text/13-ch03-3-numbers.txt:757,搜「regardless of the number's magnitude」):「同样 53 位有效数字,相对精度一致,绝对精度天差地别。」 ↩ -
出处:「3 Numbers」第 653–668 段(
text/13-ch03-3-numbers.txt:659,搜「b1 represents」):52 位从 b1 到 b52,各占 2⁻¹ 到 2⁻⁵²。 ↩ -
出处:「3 Numbers」第 673–690 段(
text/13-ch03-3-numbers.txt:673,搜「0101010101」):交替置位算出约 1.3333,再乘 2 的幂得最终值。 ↩ -
出处:「3 Numbers」第 647 段(
text/13-ch03-3-numbers.txt:647,搜「leading digit」):「规格化二进制科学计数法里,小数点前那一位永远是 1——它不可能 0,也不可能 ≥2。」 ↩ -
出处:「3 Numbers」表 3.3–3.5(
text/13-ch03-3-numbers.txt:775,搜「Table 3.3」;:840搜「Table 3.4」;:885搜「Table 3.5」)。 ↩ -
出处:「3 Numbers」第 822 段(
text/13-ch03-3-numbers.txt:822,搜「crosses a power-of-two boundary」):0.2 是 0.1 的两倍却指数不同,「就像科学计数法:0.1 ≈ 1.6×2⁻⁴,0.2 ≈ 1.6×2⁻³」。 ↩ -
出处:「3 Numbers」第 834 段与表 3.4(
text/13-ch03-3-numbers.txt:834,搜「shift 0.1's significand right by one」)。 ↩ -
出处:「3 Numbers」表 3.5 与第 923–950 段(
text/13-ch03-3-numbers.txt:923,搜「Step-by-step from right to left」;进位行见:905)。 ↩ -
出处:「3 Numbers」第 955–995 段(
text/13-ch03-3-numbers.txt:961,搜「Normalize」;:964搜「Round」;「第 53 位」的取舍见第 976–989 段两行位串的对比)。 ↩ -
出处:「3 Numbers」第 976–995 段(
text/13-ch03-3-numbers.txt:976,搜「1.0011001100110011001100110011001100110011001100110100」;精确 0.3 见:989)。差值换算见第 995 段(搜「last handful of bits」)。 ↩ -
出处:「3 Numbers」第 763–873 段的整个推演链(
text/13-ch03-3-numbers.txt:763,搜「classic example」)。「Every language using IEEE 754 has this same behavior」见第 879 段。 ↩ -
出处:「3 Numbers」第 1071–1094 段(
text/13-ch03-3-numbers.txt:1077,搜「01111111 11110000」):两个无穷的位模式与 NaN 的两个样例字节序列。 ↩ -
出处:「3 Numbers」第 1101 段(
text/13-ch03-3-numbers.txt:1101,搜「gaps between representable numbers」):「缝隙随数值变大而变大。」2⁵³ 附近间隔 2、2⁶⁰ 附近间隔 128 为机制推论(间隔 = 2^(指数−52))。 ↩ -
出处:「3 Numbers」第 1159–1258 段(
text/13-ch03-3-numbers.txt:1174,搜「Constants」;:1195搜「Operations」)。 ↩ -
出处:「3 Numbers」第 1262 段(
text/13-ch03-3-numbers.txt:1262,搜「return NaN for invalid inputs」)。「Math 方法只吃 Number」在同段。补充(不在书里,来自通用知识):Math.round 逢 .5 向正无穷方向进位,与「银行家舍入」(逢半取偶)不同。 ↩ -
出处:「3 Numbers」第 1107 段(
text/13-ch03-3-numbers.txt:1107,搜「maximum safe integer」):「最大安全整数 2⁵³−1 = 9,007,199,254,740,991;超过这一点,不是每个整数都能被精确表示。」53 位有效数字的推导见第 641 段(搜「53 bits of precision」)与本章 3.2c 节。 ↩ -
出处:「3 Numbers」第 1114–1132 段(
text/13-ch03-3-numbers.txt:1114,搜「9007199254740992」):2⁵³ 安全、2⁵³+1 不存在、2⁵³+2 安全,三个输出照抄原书。 ↩ -
出处:「3 Numbers」第 1007 段(
text/13-ch03-3-numbers.txt:1007,搜「2-52」):EPSILON = 2⁻⁵² ≈ 2.220446049250313e-16,「两个可表示数之间的最小差」。 ↩ -
出处:「3 Numbers」第 1052 段(
text/13-ch03-3-numbers.txt:1052,搜「lose precision」):「这样的运算被说成丢失精度;货币场景下,小舍入误差聚合起来可能是大钱的对错。」 ↩ -
出处:「3 Numbers」第 1052 段(
text/13-ch03-3-numbers.txt:1052,搜「not consequential」):「对许多用例,这个损失无关紧要。」判别标准是我们的提炼。 ↩ -
出处:「3 Numbers」第 1620–1631 段(
text/13-ch03-3-numbers.txt:1620,搜「invoice calculation」):发票例子即「展示给用户的每一位」的边界。 ↩ -
出处:「3 Numbers」第 38–65 段(
text/13-ch03-3-numbers.txt:38,搜「just two numeric types」):章节结构与三个角度见第 65 段(搜「three angles」)。 ↩ -
出处:「3 Numbers」第 38 段(
text/13-ch03-3-numbers.txt:38,搜「8-bit and 64-bit」):「多数语言让你在整数与小数、有符号与无符号、8 位与 64 位之间选;JavaScript 只给你 Number 和 BigInt 两种。」 ↩ -
出处:「3 Numbers」第 59 段(
text/13-ch03-3-numbers.txt:59,搜「shapes everything」):「这个单一决定塑造了一切:从金融计算差一分钱,到数过 9,007,199,254,740,991 不可靠,再到 V8 偷偷区别对待小整数。」 ↩ -
出处:「3 Numbers」第 65 段(
text/13-ch03-3-numbers.txt:65,搜「accurately add 0.1 and 0.2」)。 ↩ -
出处:「3 Numbers」第 38 段(
text/13-ch03-3-numbers.txt:38,搜「equally deceptive simplicity」):「字符串藏着自己的复杂度,数字也是一种同样具有欺骗性的简单。」对称结构是我们的提炼。 ↩ -
出处:「3 Numbers」第 763–995 段的完整推演链(
text/13-ch03-3-numbers.txt:828,搜「repeating pattern 1001」;:879搜「Every language using IEEE 754」)。五步版本是我们的压缩。 ↩ -
出处:「3 Numbers」第 879 段(
text/13-ch03-3-numbers.txt:879,搜「from Python to Java to C++」):「Every language using IEEE 754 floating-point has this same behavior, from Python to Java to C++.」 ↩