浮点数在计算机中的表示

引子#

首先,使用之前博客的程序,可以看到如下的这些实数在计算机中的计算结果是

实际数值 数值类型 计算机中的表示
102.3235 float 42CCA5A2
-3.256 float C050624E
120.254 double 405E104189374BC7
-56.2441 double C04C1F3EAB367A10

右边的计算机中的数值表示是按照《IEEE 754-2008》的标准存储数据的,具体的规定如下所示。

IEEE 754 标准#

表示形式#

IEEE 754规定了二进制浮点数在计算中的存储方式,我们以C语言中的float为例来具体说明。无论是32位系统还是64位系统,计算机中的float占用4个字节,我们就使用这些字节来存储任意的浮点数,可以参考下图

转化成对应的数学表示形式,浮点数 V

V=(1)s×M×2E

  • s 表示符号位,占据1个bit 位,如果是负数则s=1,如果是非负数则s=0;
  • M表示尾数,占据23个比特,表示有效数字,表示的数字介于1和2之间;
  • E为直属,表示基于2为基数的指数大小,占据8个比特。

因此,如果确认了上面3个参数,也就唯一确定了浮点数在计算机中的存储形式。我们以102.3235为例,来看看上面的这几个数字是如何表示出来的?

  1. 102.3235的二进制原码形式是1100110.01010010110100001110 = 1.10011001010010110100001110*2^6
  2. 确认s。因为是正数,因此 s=0。
  3. 确认M。M表示1.xxxxxx之后的xxxxxx的部分,即计算机内部保存M时,默认表示的第一位总是 1,可以舍弃表示 1 的这一位,而仅仅存储小数点之后的部分。因此 M=10011001010010110100001110,因为只能存储23个比特,将多余的位数部分截断得到M=10011001010010110100001。
  4. 确认E。它是个非负正数,按照第1步计算出来的结果,我们的指数应该是6。但是,IEEE规定根据二进制计算浮点数时,需要给指数减去一个偏置值,对于float类型这个数为127,对于double类型,这个数是1023。因此反过来,在将数字转换成二进制存储的时候,要加上这个偏置值,因此 E=6+127=133。
  5. 综合以上的所有计算结果,最后在计算机中存储的形式是01000010110011001010010110100001,转换成16进制就是42CCA5A2

特别规定#

依照上面的方法,可以依次确认其他3个浮点数的表示形式。这都是比较常规的规格化数据的处理方法,IEEE针对一些特殊的数字(绝对值特别接近0的数字或者无穷大无穷小),引入了一些特殊的规定,称为非规格化表示方法,总结如下。

上一个章节介绍的是规格化的数据,除此之外,还有非规格化的数据和特殊的数据,总结如下

  • 规格化数据。如果指数部分既不是0也不是255(指数部分既不全为0或者不全为1),就是规格化存储方式,具体的计算方法与之前介绍的相同。此时E=eBias,这里的e是指数位宽w二进制比特对应的无符号整数,Bias=2w11M=1.0+f

  • 非规格化数据。指数全为0 就是非规格化的数据。此时E=1BiasBias的值与规格化的相同,M=f。很明显,规格化数据不能表示0,非规格化的数据可以,而且0有两种表示。

  • 特殊数字。指数全为1 表示特殊的数字

    • 如果f全为0,表示无穷大,正负取决于符号s,分别表示+
    • 如果f不为0,表示这是一个非数NaN(Not a Number)。

float16的浮点表示#

参考半精度浮点数,基于上面的理解,我们可以研究下float16的一些特点。float16是用16个bit表示浮点数,不同的bit位的表示如下

因为负数和正数的值除了符号之外是对称的,所以我们仅仅研究所有的整数表示即可,将16位比特从0到2161的比特逐个写出来,可以看到如下的表格

说明 二进制比特 E M 准确值(M×2E) 十进制数
最小非规格化数 0000000000000000 -14 0×210 0.0×215 0.0
0000000000000001 -14 1×210 1×210×214 6×108
0000000000000010 -14 2×210 2×210×214 1×107
0000000000000011 -14 3×210 3×210×214 2×107
最大非规格化数 0000001111111111 -14 (2101)×210 (2101)×210×214=214224 6×105
最小规格化数 0000010000000000 -14 1+0×210 214 6.104×105
0000010000000001 -14 1+1×210 214+224 6.11×105
1 0011110000000000 0 1+0×210 1 1
最大规格化数 0111101111111111 15 1+(2101)×210 (2210)×215 65500
无穷大 0111110000000000 -- -- -- +

观察上面的表格,可以得到如下的一些结论:

  1. 相对于数学上无穷多的实数,计算机可以精确表示的实数只有有限多个,半精度浮点数可以最多表示216个数,float最多表示232个数等。

  2. 数字在数轴上的表示,越靠近0,可以表示浮点数的越稠密,相应的精度也越高,最高精度是在靠近0的非规格化数里面。假设指数的位宽是kbit,位数的位宽是n比特,那么最高精度为ϵ=2n×222k1=22n2k1,可以看出,k和n越长,精度越高,相对于n,k是精度的关键因素。下图是一个按照IEEE的标准的8bit位宽的浮点数表示图(1个bit符号位,3bit是指数位,4bit尾数位),明显可以看到在0的附近表示的数字越稠密。使用ctrl和鼠标中键放大之后,可以看到在0附近的浮点数是均匀分布的。

  3. 从数字0开始,有连续2×2n=2n+1个数是等差数列,数列的公差是ϵ,这些数就是e=[00000]e=[00001]的那些数,也就是E=1Bias的这些数。如果将它们标示在数轴上,它们是最靠近0的那部分区域,这些点均匀分布在这一块区域;

  4. 0和1都可以精确表示,而且因为符号有+-两个符号,所以0有两种表示。

  5. 最大非规格化数和最小化规格数相差一个ϵ,这个差值与非规格数的之间的差值相同,二者平滑过渡。

  6. 可以表示的最大规格化数是(12n1)×22k1;

  7. 如果将二进制比特看成u16的数,那么这些数本身表示的u16的数据大小与它们表示的float的大小关系相同,都是递增的。

下面的代码可以将所有的半精度浮点数的所有非负数表示出来(严格得说,不包括-0.0),可以都打印出来体会下上面的结论

# -*- coding: utf-8 -*-
import matplotlib.pyplot as plt
import numpy as np

udata = np.uint16(0)
p1 = [] # all of non-negative float16 number
for i in range(2**15):
    p1.append(udata.view(np.float16))
    udata += 1
    udata = np.uint16(udata)

参考资料#

posted @   bugxch  阅读(959)  评论(2编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App
· 张高兴的大模型开发实战:(一)使用 Selenium 进行网页爬虫
点击右上角即可分享
微信分享提示
主题色彩