IEEE 754 浮点数位模式可视化工具

将十进制小数分解为IEEE 754单精度(32位)或双精度(64位)的位模式并可视化展示,也可将位模式反向解码为十进制值。符号位、指数部、尾数部分色显示,并支持识别NaN、无穷大与非规格化数。

经典案例:为什么 0.1 + 0.2 不等于 0.3

在JavaScript中计算 `0.1 + 0.2` 得到的是 `0.30000000000000004`,而非 `0.3`。查看双精度的位模式即可理解原因。

数值 双精度(64位)十六进制 双精度(64位)二进制
0.1 0x3FB999999999999A 0011111110111001100110011001100110011001100110011001100110011010
0.2 0x3FC999999999999A 0011111111001001100110011001100110011001100110011001100110011010
0.3 0x3FD3333333333333 0011111111010011001100110011001100110011001100110011001100110011
0.1 + 0.2 0x3FD3333333333334 0011111111010011001100110011001100110011001100110011001100110100

0.1、0.2、0.3在二进制中都是无限循环小数,无法完全放入52位的尾数部分,因此都会被四舍五入。由于0.1与0.2各自的舍入误差方向恰好没有抵消,两者相加的结果与0.3的精确位模式相比只相差最后1位(0x3FD3333333333334)。可以在上方的转换工具中亲自输入这些数值,比较末位的差异。

使用提示

  • 将0.1、0.2、0.3分别以双精度转换为位模式,并与0.1+0.2的结果(0.30000000000000004)对比,可以直观地发现只有最后1位不同。
  • 在十进制输入框中输入"-0",会得到只有符号位为1的位模式(与"+0"不同)。作为数值0===-0成立,但位表示本身是不同的。
  • 当指数部全为0时,尾数部为0表示零,尾数部不为0则表示非规格化数——一种以牺牲精度为代价来表示极接近零的数值的特殊表示法。
  • 将同一数值(如0.1)在单精度(32位)与双精度(64位)之间切换,可以确认单精度的舍入误差更大。
  • 将位模式解码为十进制时,需要输入以`0x`开头的十六进制字符串,或位数与所选精度完全一致的二进制字符串(`0b`前缀可省略)。

常见问题

0.1、0.2、0.3在二进制中都是无限循环小数,计算机必须将它们四舍五入以适应有限位数的尾数部(双精度为52位)。由于0.1与0.2各自的舍入误差方向恰好没有相互抵消,两者相加的结果与0.3的精确位模式略有不同(结果为0.30000000000000004)。这并非某个特定编程语言的缺陷,而是所有遵循IEEE 754标准的语言共有的现象。

单精度(float,32位)由1位符号位、8位指数部、23位尾数部组成;双精度(double,64位)由1位符号位、11位指数部、52位尾数部组成。尾数部位数越多,有效数字(精度)越高;指数部位数越多,可表示的数值范围越广。大多数编程语言的浮点数默认类型都是双精度。

指的是指数部全为0但尾数部不为0的位模式。通常的(规格化的)浮点数在尾数开头带有隐含的1,而非规格化数省略了这一隐含1,转而将指数固定为最小值,从而能够表示比通常最小值更接近零的数值,但代价是牺牲了有效精度。

IEEE 754中符号位是独立存在的一位,因此即便指数部与尾数部都全为0,符号位为0时得到"+0",为1时得到"-0",形成两种不同的位模式。作为数值比较时0===-0成立,但在某些运算结果中会显现差异,例如1/0得到+Infinity,而1/-0得到-Infinity。

两者都通过指数部全为1的位模式表示。若尾数部全为0,则表示无穷大(通过符号位区分+Infinity与-Infinity);若尾数部不为0,则表示NaN(Not a Number,非数)。它们由0除法、∞-∞等数学上未定义的运算产生。
ツールくん

闲话 ― IEEE 754如何成为一门"通用语言"

1985年以前,各计算机制造商都采用各自独立的浮点数表示法。IBM、DEC、Cray等公司即便计算同一个"0.1",其舍入方式与可表示的数值范围也会存在细微差异,导致在一台机器上编写的数值计算程序移植到另一台机器时,结果往往会出现微小偏差。为了解决这一混乱局面,曾参与英特尔8087数值协处理器设计的加州大学伯克利分校学者William Kahan牵头组织了跨行业的标准化工作,于1985年发布了IEEE 754标准。

IEEE 754的核心思想,是将有限的位数(32位或64位)划分为符号、指数部、尾数部三个部分的分工,以此近似表示无限多的实数。指数部负责数值的量级(大小规模),尾数部负责有效数字的精度,这样即使位数有限,也能灵活覆盖从极小到极大的数值范围。指数部之所以要加上偏移量(单精度为127、双精度为1023)后再存储,是为了避免像有符号整数的补码那样复杂的符号处理,只需将位串作为普通整数直接比较即可判断数值大小关系。

在二进制中能够被"精确"表示的小数,仅限于分母为2的幂的分数(如1/2、1/4、3/8等)。十进制的0.1写成分数是1/10,由于10不是2的幂,在二进制中会变成0.0001100110011...这样无限循环的小数。由于尾数部的位数有限,只能在某处截断并四舍五入,这正是"计算机无法精确表示0.1"的根本原因。需要精确处理十进制小数的会计系统之所以往往不使用浮点数,而改用整数运算或专门的十进制类型(如BCD),原因也在于此。

IEEE 754此后在2008年、2019年经历了修订,新增了半精度(16位)、四倍精度(128位)以及十进制浮点数等规定,但符号、指数部、尾数部三分结构以及舍入模式的核心思想,自1985年初版以来从未改变。由于几乎所有编程语言的float/double类型都遵循这一标准,"为什么0.1+0.2不等于0.3"这一疑问,无论在哪种编程语言中都会以相同的形式出现。