IEEE 754规范
IEEE
IEEE
(Institute of Electrical and Electronics Engineers,简称为IEEE)中文称之为电气电子工程师学会,基本也就叫IEEE,读作i triple e
。IEEE
是一个建立与1963年1月1日的国际性电子技术和电子工程师协会,是世界最大的专业技术组织之一。目前IEEE在工业界所定义的标准有着极大的影响。
IEEE-754 规范即是一个比较有影响的标准。
在六、七十年代,各家计算机公司的各个型号的计算机,有着千差万别的浮点数表示,却没有一个业界通用的标准。这给数据交换、计算机协同工作造成了极大不便。IEEE的浮点数专业小组于七十年代末期开始酝酿浮点数的标准。在1980年,英特尔公司就推出了单片的8087浮点数协处理器,其浮点数表示法及定义的运算具有足够的合理性、先进性,被IEEE采用作为浮点数的标准,于1985年发布。而在此前,这一标准的内容已在八十年代初期被各计算机公司广泛采用,成了事实上的业界工业标准。加州大学伯克利分校的数值计算与计算机科学教授威廉·卡韩被誉为“浮点数之父”。
IEEE-754
首先看两个数字的表示方式:
以上均是以10为底的指数表示方式。
同理是否可以用2为底的指数形式来表示:
所以 IEEE-754的实现方式其实就是以2为底指数来表达一个数字。
image-20200524231441051整个数字表达分为三部分,有三部分来确定一个数值是多少。
- sign:符号位 0为正,1为负
- exponent:指数数字
- fraction:分数或小数
当然这里涉及到个位数所占数量的多少,IEEE-754
针对不同位数有相应的规范。
精度 | 符号位(S) | 指数位(E) | 分数位(M) | 总位数 |
---|---|---|---|---|
单精度 | 1 | 8 | 23 | 32 |
双精度 | 1 | 11 | 52 | 64 |
IEEE的表达式如下:
演示一个例子:
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
这里需要考虑到一个精度的问题,因为单精度在设计时,就限定了指数范围以及分数的位数,所以当表达一些很小的数字是就会面临精度丢失的问题。所以设计的时候还需要考虑规约(Normalized Number
) 和非规约 (Denormalized Number
)两种场景,规约形式默认有一位隐形有效数字1,比如以下一个数字的两种表达:
从而非规约形式能表达的更小的数据。
精度的问题
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的双精度浮点数编码,同样面临精度问题
因为Javascript
仅适用Number类型存储数字类型,那么对于通常意义上的Long类型存储也存在精度丢失的问题,正因为IEEE754双精度限制了分数位52位,即52位有效位,如果二进制超过52位的数据将存在精度丢失,即最大安全数字为
参考: