2、原码、反码、补码、浮点类型精度损失

188 阅读2分钟

在了解浮点数为什么精度损失之前,首先应该熟悉符点类型是怎么在计算机中存储的,应该了解数值类型的二进制保存方式

1、二进制原码、反码、补码

  • 正数的原码=反码=补码 原码的计算对2取余

66的二进制是1000010 66%2 = 33-----------0 33%2 = 16-----------1 16%2 = 8-----------0 8%2 = 4-----------0 4%2 = 2-----------0 2%2=1-----------0 1%2=0-----------1 计算结果为1000010

  • 负数的二进制仍然是该数的二进制,不过符号位为1,负数的反码=原码符号位不变,数值位取反,负数的补码=反码+1

-66的原码:1 100 0010 反码:符号位不变,数值位取反。10111101 补码:反码加1。10111110

  • 计算机都是以补码的形式进行存储的。补码存储统一了零的编码,并且将减法转变为加法的运算

2、浮点数的存储及精度损失

浮点数的存储借鉴了科学计数法,分为符号位、指数位和尾数位。按照国际标准IEEE 754,任意一个二进制浮点数可以表示成: V=(−1)s × M × 2E 其中:

  • (−1)s表示符号位,s=0表示正数,s=1表示负数
  • M表示有效数字,1=<M<2
  • 2E表示指数位 image.png

float类型存储示意图 其中最高位为符号位,接着8位为指数位,剩余的为有效数字double类型同样最高位为符号位,接着11个指数位,52个有效数字位。


  • 符点类型在存储的时候由于有效数字位1=<M<2,所以规定在存储的时候会舍弃1。
  • 指数E的存储的为无符号整数,及取值范围为【0,255】,但由于指数也可以是负数,所以在存入的时候要加上127,使用时也要相应的减去127
  • 当指数位全部为0的时候,表示很小的小数,此时有效数字位M不补1
  • 当指数位全部为1的时候,表示无穷大

符点数的存储 38.95 先装化成定点数----> 38%2=19---------0 19%2=9---------1 9%2=4---------1 4%2=2---------0 2%2=1---------0 1%2=0---------1 ------------>100110 0.95 x 2=1.9---------1 0.9 x 2=1.8---------1 0.8 x 2=1.6---------1 0.6 x 2=1.2---------1 0.2 x 2=0.4---------0 0.4 x 2=0.8---------0 ......会发现造成里循环,这也就是符点类型精度损失的原因

  • 当我们表示有效数字位的时候会用二进制表示小数,但是有可能会进入死循环,此时float会截断23位,此时就会造成精度损失。在浮点数的计算中也是相同的原理

3、解决方案

Java中对浮点数的解决方案是引入java.math.BigDecimal来进行精确的计算,但是计算的时候要注意选用string类型的构造方法

double d1 = 0.06;  
double d2 = 0.01;  
BigDecimal b1 = new BigDecimal(d1);  
BigDecimal b2 = new BigDecimal(d2);  
System.out.println(b1.add(b2).doubleValue());
//运行结果:0.06999999999999999
  • 造成这种差异的原因是我们传入的并不是0.01,0.01用浮点数表示会造成精度损失,所以构造方法接受的参数并不是0.01
  • 解决方法:我们在使用bigdeciml的时候最好选用string类型的构造器