IEEE 754规范

2020-05-27  本文已影响0人  游杜渐

IEEE

IEEE(Institute of Electrical and Electronics Engineers,简称为IEEE)中文称之为电气电子工程师学会,基本也就叫IEEE,读作i triple eIEEE是一个建立与1963年1月1日的国际性电子技术和电子工程师协会,是世界最大的专业技术组织之一。目前IEEE在工业界所定义的标准有着极大的影响。

IEEE-754 规范即是一个比较有影响的标准。

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

IEEE-754

首先看两个数字的表示方式:
120000 = 1.2 * 10 ^ 5

5.5 = 5.5 * 10 ^{1}

以上均是以10为底的指数表示方式。

同理是否可以用2为底的指数形式来表示:
120000 = 1.46484375 * 2^{13}

5.5 = 1.375 * 2^{2}

所以 IEEE-754的实现方式其实就是以2为底指数来表达一个数字。

image-20200524231441051

整个数字表达分为三部分,有三部分来确定一个数值是多少。

当然这里涉及到个位数所占数量的多少,IEEE-754 针对不同位数有相应的规范。

精度 符号位(S) 指数位(E) 分数位(M) 总位数
单精度 1 8 23 32
双精度 1 11 52 64

IEEE的表达式如下:
V = (-1)^s * M * 2^E

演示一个例子:

                Float f = 0.025F;
        System.out.println(Integer.toBinaryString(Float.floatToIntBits(f)));
                // 0011 1100 1100 1100 1100 1100 1100 1101

将二进制以单精度进行划分后(1+8+23):

【0】【011 1100 1】【100 1100 1100 1100 1100 1101】
S = 0
E = 121 - 127 (IEEE754以无符号存储,单精度每个都加了127,称之为偏移量Bias)
M = 0.6000000238418579 + 1

M = 2^{-1} + 2^{-4}+ 2^{-5}+ 2^{-8}+ 2^{-9}+ 2^{-12}+ 2^{-13}+ 2^{-16}+ 2^{-17}+ 2^{-20}+ 2^{-21}+ 2^{-23}

0.025_{10} = 1.6000000238418579 * 2^{-6}

这里需要考虑到一个精度的问题,因为单精度在设计时,就限定了指数范围以及分数的位数,所以当表达一些很小的数字是就会面临精度丢失的问题。所以设计的时候还需要考虑规约(Normalized Number) 和非规约 (Denormalized Number)两种场景,规约形式默认有一位隐形有效数字1,比如以下一个数字的两种表达:
10.101 = 1.0101 * 2^{10} (规约形式)

10.101 = 0.010101 * 2^{1000} (非规约形式)

从而非规约形式能表达的更小的数据。

精度的问题

1、在Java语言中,对于Float使用了单精度的存储,Double场景使用了双精度存储。

System.out.println(0.1d + 0.2d);
//输出 0.30000000000000004

来看下0.1和0.2的表示方式

0.1的二进制转换
0.1*2=0.2  >0
0.2*2=0.4  >0
0.4*2=0.8  >0
0.8*2=1.6  >1
0.6*2=1.2  >1
0.2*2=0.4  >0
0.4*2=0.8  >0
0.8*2=1.6  >1
0.6*2=1.2  >1
0.2*2=0.4  >0
0.4*2=0.8  >0
0.8*2=1.6  >1
0.6*2=1.2  >1
...
无限循环 0.0001100110011...
0.2的二进制转换
0.2*2=0.4  >0
0.4*2=0.8  >0
0.8*2=1.6  >1
0.6*2=1.2  >1
0.2*2=0.4  >0
0.4*2=0.8  >0
0.8*2=1.6  >1
0.6*2=1.2  >1
0.2*2=0.4  >0
0.4*2=0.8  >0
0.8*2=1.6  >1
0.6*2=1.2  >1
...
无限循环 0.001100110011...

十进制0.1和0.2用二进制表达式会展示成无限循环小数,类似10进制里的1/3。因为存储的位数是有限的,那么势必会丢失精度。

Java上使用BigDecimal来计算能避免浮点数计算丢失精度的问题,BigDecimal并没有使用二进制的浮点数来进行计算,而是将浮点数的整数部分和复数部分单独计算,保证了计算时的精度问题(不要使用)。

//正确的姿势:
System.out.println(new BigDecimal("0.1").add(new BigDecimal("0.2")));
//输出:0.3

//错误的姿势:
System.out.println(new BigDecimal(0.1).add(new BigDecimal(0.2)));    
//输出:0.3000000000000000166533453693773481063544750213623046875

2、Javascript 场景中仅用了Number一个数值类型,采用的是IEEE-754的双精度浮点数编码,同样面临精度问题
0.1 + 0.2 ≠ 0.3
因为Javascript 仅适用Number类型存储数字类型,那么对于通常意义上的Long类型存储也存在精度丢失的问题,正因为IEEE754双精度限制了分数位52位,即52位有效位,如果二进制超过52位的数据将存在精度丢失,即最大安全数字为
2^{53} - 1

参考:

电气电子工程师学会

IEEE-754 在线计数器

IEEE754揭秘浮点数

你应该知道的浮点数基础知识

上一篇下一篇

猜你喜欢

热点阅读