写在前面
碰巧最近定义接口的时候碰到了浮点数精度的问题,稍微整理了浮点数的一些知识点:
- 浮点数的底层表示
- 浮点数的精度损失问题
- 浮点数的表示范围和精度
小数的二进制表示
浮点数的表示借鉴了科学计数法,比如在十进制中311.56可以表示成。类似地,浮点型数据的二进制存储结构也可以被划分成:符号位 + 指数位 + 尾数位。按照国际标准IEEE 754,任意一个二进制浮点数可以表示成:
其中:
- 表示符号位,表示正数,表示负数
- 表示有效数字,
- 表示指数位
指数部分决定了数的大小范围,有效数字部分决定了数的精度。
举两个简单的例子:
十进制 | 二进制 | 二进制科学计数法 | S | E | M |
---|---|---|---|---|---|
3.0 | 11.0 | 1.1 x 2^1 | 0 | 1 | 1.1 |
-5.0 | -101.0 | -1.01 x 2^2 | 1 | 2 | 1.01 |
浮点数在计算机底层的存储机制
double类型和float类型(可能还有long double类型)在计算机的底层存储结构都是一致的,唯一的不同在于float是32位而double是64位的。
无论什么数据,在计算机内存中都是以01存储的,浮点数也不例外。
0. 定点数
计算机中小数的表示按照小数点的位置是否固定可以分为浮点数和定点数。为了方便和float32浮点数做对比,我们构造一个32位精度的定点数,其中小数点固定在23bit处:
定点数的底层表示从定点数的存储上看,它表示的数值范围有限(以小数点在23bit为例,整数部分仅有8位,则整数部分取值范围是0~255),但好在处理定点数计算的硬件比较简单。
1. IEEE 754x浮点数
以32位浮点数为例,最高一位是符号位s,接着的8位是指数位E,最后的23位是有效数字M。double64最高一位是符号位,有11个指数位和52个有效数字位。下图展示了float32类型的底层表示:
float的底层表示
其中IEEE 754的规定为:
- 因为M的取值一定是,因此规定M在存储时舍弃第一个1,只存储小数点之后的数字,这样可以节省存储空间(以float32为例,可以保存23位小数信息)
- 指数E是一个无符号整数,因此它的取值范围为0到255,但是指数可以是负的,所以规定在存入E时在它原本的值加上127(使用时减去中间数127),这样E的取值范围为-127到128
- E不全为0,不全为1:正常的计算规则,E的真实值就是E的字面值减去127(中间值),M加回1
- E全为0:指数E等于1-127为真实值,M不加回1(此时M为0.xxxxxx),这样是为了表示0和一些很小的数
- E全为1:M全为0表示正负无穷大(取决于S符号位);M不全为0时表示不是一个数(NaN)
2. 具体例子
以78.375为例,它的整数和小数部分可以表示为:
因此二进制的科学计数法为:
一般而言转换过程包括如下几步:
- 改写整数部分:将整数部分的十进制改写成二进制
- 改写小数部分:拆成到的和
- 规格化:保证小数点前只有一位是1,改写成二进制的科学计数法表示
- 填充:指数部分加上127(中间数)填充E;有效数字部分去掉1后填充M
按照前面IEEE 754的要求,它的底层存储为:
image.png3. 精度损失
十进制中的0.5(代表分数1/2)表示二进制中的0.1(等同于分数1/2),我们可以把十进制中的小数部分乘以2作为二进制的一位,然后继续取小数部分乘以2作为下一位,直到不存在小数为止。以0.2这个无法精确表示成二进制的浮点数为例:
因此十进制下的0.2无法被精确表示成二进制小数,这也是为什么十进制小数转换成二进制小数时会出现精度损失的情况。
浮点型表示精度和范围
- 单精度浮点数float占四个字节,表示范围-3.40E+38 ~ +3.40E+38
- 双精度浮点数double占八个字节,表示范围-1.7E+308 ~ +1.79E+308
以float为例,能表示的最大二进制数据为(小数点后为23个1),而二进制下,因此能表示的最大十进制数据是:
从二进制小数的科学计数法表示上看,可以知道float的精度为,double的精度为。
Reference
[1] https://q.115.com/182920/T1268124.html
[2] https://blog.csdn.net/u014470361/article/details/79820892
[3] https://www.cnblogs.com/wangsiting1997/p/10677805.html
[4] https://blog.csdn.net/u014470361/article/details/79820892
网友评论