浮点数在计算机中的编码方式

一、前言

我们常能听到,直接用浮点数做运算得出的结果是不准确的了;或者也能看到涉及到浮点数时,会出现一些奇奇怪怪的问题,比如:

public class DecimalTest {
    public static void main(String[] args) {
        float f1 = 1.2002f;
        float f2 = 100000.2002f;
        System.out.println(f1);
        System.out.println(f2);
    }
}

=============
输出:
1.2002
100000.2

那么这些现象的底层原理是什么呢?这需要从浮点数在计算机中的编码方式说起。

二、IEEE浮点标准

表示方式

IEEE浮点标准是广泛使用的浮点数编码方式,其用下面的方式表示一个数(和10进制的科学计数法是几乎相同的):

V=(-1)^{s}*M*2^{E}

  • 符号位 s,s = 0为正数,s = 1为负数
  • 尾数 M,一个二进制小数,取值范围一般为 [1,2)
  • 阶码 E,表示 2 的 E 次幂
储存方式

在实际存储浮点数时,M和E做了些特殊的编码处理,下面以32位的单精度浮点数为例(即编程语言中的float)。

  • 31位的 s 部分:符号位 s,s = 0为正数,s = 1为负数
  • 30-23位的 exp 部分:由阶码 E 转换而来,转换方式(其中 k 为此部分的 bit 数,即8):
    e(编码值) = Bias(偏移量) + E(阶码) = 2^(k-1) - 1 + E = 127 + E
  • 22-0位的 frac 部分:M 的小数部分,即 M-1

补充说明:

  • 上述为常用的“规格化的值”的实现方式,实际上完整的IEEE标准包括 规格化的值、非规格化的值、特殊值3种情况
  • 三种情况通过 exp 部分既不全为0也不全为1、全为0、全为1进行区分
  • 规格化的值用于表示普通常用的值,非规格化的值用于表示非常接近于0的数,特殊值用于表示无穷、非合法数字等情况

三、开头的问题

为什么 100000.2002f 写入 float 后,发生精度丢失变成了 100000.2?

  1. 100000.2002 的二进制原码为 11000011010100000.001100110100000001......
  2. 转成 IEEE 编码,V=(-1)^{0}*1.1000011010100000001100110100000001...*2^{16}
  3. 即s=0,exp=127+16=143=10001111,farc=10000110101000000011001(1)=10000110101000000011010(向偶数舍入)。
  4. 最终的编码为:0-10001111-10000110101000000011010,对应的数值为:1.10000110101000000011010*2^16 = 11000011010100000.0011010 = 10000.203125

四、其他

  • 快速判别一个小数是否可被二进制精确表示:该小数是否可表示为以 2^{n} 为分母的分数的和
  • 主要参考资料:《深入理解计算机系统》第二章,2.4
  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值