IEEE754简介

513 阅读12分钟

阅读完一下文章可以达成一下能力:

  1. IEEE格式数据转为正常十进制
  2. 整数十进制转为IEEE数据格式
  3. 知道如何舍入
  4. 知道为什么0.1+0.2 !== 0.3
  5. 知道js场景静态变量的值到底是怎么来的

1. 什么是IEEE 754

IEEE 二进制浮点数算术标准IEEE 754)是20世纪80年代以来最广泛使用的浮点数运算标准,为许多CPU浮点运算器所采用。这个标准定义了表示浮点数的格式(包括负零-0)与反常值(denormal number),一些特殊数值((无穷(Inf)与非数值(NaN)),以及这些数值的“浮点数运算符”;它也指明了四种数值舍入规则和五种例外状况(包括例外发生的时机与处理方式)。

IEEE 754规定了四种表示浮点数值的方式:单精确度(32位)、双精确度(64位)、延伸单精确度(43比特以上,很少使用)与延伸双精确度(79比特以上,通常以80位实现)。只有32位模式有强制要求,其他都是选择性的。大部分编程语言都提供了IEEE浮点数格式与算术,但有些将其列为非必需的。例如,IEEE 754问世之前就有的C语言,现在包括了IEEE算术,但不算作强制要求(C语言的float通常是指IEEE单精确度,而double是指双精确度)。

该标准的全称为IEEE二进制浮点数算术标准(ANSI/IEEE Std 754-1985) ,又称IEC 60559:1989,微处理器系统的二进制浮点数算术(本来的编号是IEC 559:1989)[1]。后来还有“与基数无关的浮点数”的“IEEE 854-1987标准”,有规定基数为2跟10的状况。现在最新标准是“ISO/IEC/IEEE FDIS 60559:2020”。

2. 为什么会选择它

在六、七十年代,各家计算机公司的各个型号的计算机,有着千差万别的浮点数表示,却没有一个业界通用的标准。这给数据交换、计算机协同工作造成了极大不便。IEEE的浮点数专业小组于七十年代末期开始酝酿浮点数的标准。在1980年,英特尔公司就推出了单片的8087浮点数协处理器,其浮点数表示法及定义的运算具有足够的合理性、先进性,被IEEE采用作为浮点数的标准,于1985年发布。而在此前,这一标准的内容已在八十年代初期被各计算机公司广泛采用,成了事实上的业界工业标准加州大学伯克利分校的数值计算与计算机科学教授威廉·卡韩被誉为“浮点数之父”。

3. IEEE浮点标准表示

IEEE浮点标准用 V=(1)s×M×2EV=(-1)^{s} \times M\times 2^E 形式来表示一个数:

  • 符号(sign) ss决定这数是负数(ss = 1)还是正数(ss=0),而对于数值0的符号位解释作为特殊情况处理。
  • 尾数(significand) MM 是一个二进制小数,它的范围是 1-2-𝜀或者是0-1-𝜀。
  • 阶码(exponent) EE 的作用是对浮点数加权,这个权重是 2 的 EE 次幂(可能是负数)。

将浮点数的位表示划分为三个字段,分别对这些值进行编码:

  • 一个单独的符号位s直接编码符号s。
  • kk位的阶码字段exp=ek1e1e0exp=e_{k-1}\cdot\cdot\cdot e_{1}e_{0}
  • nn位小数字段frac=fn1f1f0frac=f_{n-1}\cdot\cdot\cdot f{1}f{0} 编码尾数MM,但是编码出来的值也依赖于阶码字段的值是否等于0.

单精度浮点格式中,总共为32位,s、exp和frac字段分别为1、8、23位。双精度浮点各种中,总共为64位,s、exp和frac字段分别为1、11、52位。

单精度

BossHi_20230829114213048.jpg

双精度

BossHi_20230829115118888.jpg

根据 exp 的值,被编码的值可以分成三种不同的情况(最后一种情况有两个变种)。

BossHi_20230829115257517.jpg

情况1:规范化的值

当 exp 的位模式既不全为0,也不全为1,即属于这种情况。阶码字段解释为以偏置(biased)形式表示的有符号整数。阶码的值是 E=eBiasE=e - Bias,其中e即为无符号数,其位表示为ek1e1e0e_{k-1}\cdot\cdot\cdot e_{1}e_{0},而BiasBias是一个等于2k112^{k-1}-1的偏置值(单精度为127,双精度为1023)。

小数字段frac被解释为小数值ff,其中0f10≤f<1,其二进制表示为 0.fn1f1f00.f_{n-1}\cdot\cdot\cdot f_{1}f_{0}。尾数定义为M=1+fM=1+f

情况2:非规范化的值

当阶码为全0时,所表示的数是非规范化形式。在这种情况下,阶码值是 E=1BiasE=1-Bias ,尾数的值 M=fM=f

情况3:特殊值

当阶码全为1时。

  • 当小数全为0时,表示无穷,s=0时是+,当s=1时是当s=0时是+\infty ,当s=1时是-\infty
  • 当小数不为0时,表示NaN

数据示例

BossHi_20230829163644921.jpg

舍入

偏差

对于舍入,可以有很多种规则,可以向上舍入,向下舍入,向偶数舍入。如果我们只采用前两种中的一种,就会造成平均数过大或者过小,实际上这时候就是引入了统计偏差。如果是采用偶数舍入,则有一半的机会是向上舍入,一半的机会是向下舍入,这样子可以避免统计偏差。而 IEEE 754 就是采用向最近偶数舍入(round to nearest even)的规则。

舍入规则

舍入的规则需要区分三种情况,

  • 当具体的值大于中间值的时候,向上舍入
  • 当具体的值小于中间值的时候,向下舍入
  • 当具体的值等于中间值的时候,向偶数舍入

向偶数舍入指的是要保留的最低有效位为偶数,具体规则,

  • 要保留的最低有效位如果为奇数,则向上舍入
  • 要保留的最低有效位如果为偶数,则向下舍入

中间值

上面的舍入规则,提到了一个很重要的概念,中间值。怎样才能确定这个中间值呢?这也是我花了最多时间理解的。

要找到中间值,先确定要保留的有效数字,找到要保留的有效数字最低位的下一位。如果这位是进制的一半,而且之后的位数都为 0,则这个值就是中间值。

上面的描述比较抽象,来看两个例子

  • 十进制的 1.2500,要保留到小数点后一位,下一位是 5,是进制的一半,后面位数都为 0,所以这个值就是中间值
  • 二进制的 10.0110,要保留到小数点后两位,下一位是 1,是进制的一半,后面位数都为 0,所以这个值就是中间值

例子

知道了舍入规则之后,我们来看几个具体的例子,以二进制为例,有效位数保留到小数点后两位

  • 10.00011,中间值为 10.00100,小于中间值,向下舍入为 10.00
  • 10.00110,中间值为 10.00100,大于中间值,向上舍入为 10.01
  • 10.11100,中间值为 10.11100,等于中间值,要保留的最低有效位 1 为奇数,向上舍入为 11.00
  • 10.10100,中间值为 10.10100,等于中间值,要保留的最低有效位 0 为偶数,向下舍入为 10.10

十进制整数转二进制

十进制整数转换为二进制整数采用"除2取余,逆序排列"法。具体做法是:用2去除十进制整数,可以得到一个商和余数;再用2去除商,又会得到一个商和余数,如此进行,直到商为零时为止,然后把先得到的余数作为二进制数的低位有效位,后得到的余数作为二进制数的高位有效位,依次排列起来。

image.png

十进制小数转二进制

十进制小数转换成二进制小数采用"乘2取整,顺序排列" 法。具体做法是:用2乘十进制小数,可以得到积,将积的整数部分取出,再用2乘余下的小数 部分,又得到一个积,再将积的整数部分取出,如此进行,直到积中的小数部分为零,或者达到所要求的精度为止。

然后把取出的整数部分按顺序排列起来,先取的整数作为二进制小数的高位有效位,后取的整数作为低位有效位。

image.png

应用

1. 为什么0.1 + 0.2 !== 0.3

解析:JS 中的数字都是 IEEE 754 64 位双精度浮点数,进行十进制运算大体分三步:

  1. 十进制转二进制浮点数
  2. 二进制数运算
  3. 二进制结果转十进制

首先完成 进制转换

// 尾数

0.1 => 0.0 0011 0011 0011 0011 0011 0011 0011 0011 .... 循环

0.2 => 0.0 0110 0110 0110 0110 0110 0110 0110 0110 .... 循环

// 规格化

0.1 => 242^{-4} × 1.1001 1001 1001 1001 1001 1001 1001 .... 循环

0.2 => 232^{-3} × 1.1001 1001 1001 1001 1001 1001 1001 .... 循环

// 转 64 位浮点数标准格式,尾数就近舍入到 52 位,此处发生精度损失

0.1 => 0,011 1111 1011,1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001(1)

0.1 => 0,011 1111 1011,1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1010(取最近偶数)

0.2 => 0,011 1111 1100,1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1010

接下来进行二进制运算,由于阶数不同,先 对阶小阶向大阶看齐,小阶尾数右移 阶差 位。之所以不左移是因为左边为高位,丢掉损失很大。

0.1 尾数右移 1 位,左边移入隐含的 1,右边移出一个 0,再次产生误差

0.1 => 0,011 1111 1100,1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1101(0)

0.1 => 0,011 1111 1100,1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1101(取最近)

0.2 => 0,011 1111 1100,1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1010

接下来进行 尾数求和

  0.1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1101

+ 1.1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1010

--------------------------------------------------------------------

 10.0110 0110 0110 0110 0110 0110 0110 0110 0110 0110 0110 0110 0111

将尾数 规格化,指数加 1,尾数右移 1 位,就近 舍入,再次产生误差。

1.0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011(1) x 2

1.0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0100 x 2 (取最近偶数)

转为标准格式。

0.1 + 0.2 = 0,011 1111 1101,0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0100

      0.3 = 0,011 1111 1101,0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011

可以看出两者相差 2542^{-54},并非完全相等。

2. JavaScript 中 Number 对象的几个静态属性

Number.EPSILON

1与距离其最近的可表示的浮点数的差值

2.2204460492503130808472633361816 × 1016=25210^{-16} = 2^{-52}

Number.MAX_VALUE

可以表示的最大有限数 0, 111 1111 1110, 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111

M=1+fM = 1 + f

f=1252f = 1 - 2^{-52}

M=2252M = 2 - 2^{-52}

Bias=1023Bias = 1023

e=2112=2046e = 2^{11}-2 = 2046

E=eBias=20461023=1023E = e - Bias = 2046 - 1023 = 1023

V=(1)s×M×2EV=(-1)^{s} \times M\times 2^E 可得

V=1×(2252)×21023=1.7976931348623157e+308V = 1 × (2 - 2^{-52}) × 2^{1023} = 1.7976931348623157e+308

Number.MIN_VALUE

可以表示的最小有限数

0, 000 0000 0000, 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001

M=fM = f

f=252f = 2^{-52}

M=252M = 2^{-52}

Bias=2k11=1023Bias = 2^{k-1} - 1 = 1023

E=1Bias=11023=1022E = 1 - Bias = 1 - 1023 = -1022

V=(1)s×M×2EV=(-1)^{s} \times M\times 2^E 可得

V=1×252×21022=21074V = 1 × 2^{-52} × 2^{-1022} = 2^{-1074} = 5e-324

Number.MAX_SAFE_INTEGER

可以精确表示和比较的最大整数 252×(2252)2^{52} × (2-2^{-52})

0, 100 0011 0011, 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111

Number.MIN_SAFE_INTEGER

可以精确表示和比较的最大整数 252×(2252)-2^{52} × (2-2^{-52})

1, 100 0011 0011, 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111

为什么最大的数要小于2532^{53}呢?这个数是拍脑袋定的吗?

参考文档:

  1. book.douban.com/subject/269…
  2. logi.im/back-end/th…
  3. blog.leodots.me/post/45-iee…