探究浮点数

浮点数有什么可研究的

先看代码

为何明明看起来很”整”的”小数”,并不像我们想象中,而是要么多了点儿,要么少了点儿,为什么会有这个情况发生?

何为浮点数

先问大家一个问题: 浮点数等价于小数吗?

那么,我们先了解下浮点数与对应的定点数

定点数

小数点在计算机中通常有两种表示方法,一种是约定所有数值数据的小数点隐含在某一个固定位置上,称为定点表 示法,简称 定点数 ;

通常将小数点的位置固定在数据的最高位之前, 一般常称为定点小数(纯小数)

或者是固定在最低位之后,称为定点整数(纯整数)。

浮点数

数值的小数点可以在不同位置上任意浮动,称为浮点表示法,简称浮点数;
而通常小数可以用科学计数法表示,小数点位置可以任意移动,因此小数也被很多人直接等同于浮点数.

计算机中的浮点数

浮点数的存储方式

C语言中,对于浮点类型的数据采用单精度类型(float)和双精度类型(double),按照浮点数存储标准(IEEE 制定)来存储,float数据占用32bit,double数据占用64bit

无论是单精度还是双精度在存储中都分为三个部分:
符号位(Sign) : 0代表正,1代表负
指数位(Exponent):用于存储科学计数法中的指数数据,并且采用移位存储方式
尾数部分(Mantissa):尾数部分

其中单精度的存储方式如下图所示:

而双精度的存储方式为:

PS:以下没有特别声明的研究都建立在单精度上

而IEEE都是用科学计数法来存储数据的,9.125存储为:9.125*10^0.

在十进制中,指数的基数位10,并且表示小数点移动多少位以生成系数.

但是,计算机根本不认识十进制的数据,他只认识0,1.所以在计算机存储中,首先要将上面的数更改为二进制的数据,相应的,科学计数法也要改成二进制的.

系数和指数基于二进制,那么用 XXX*2^N 即可

单精度尾数有23bit, 但是加上固定的一位整数位1,实际可表示精度的有24bit.
而根据9==>1001二进制转换,4个 bit 可以表示1位十进制.所以24bit/4 = 6位.
所以. float 可以精度到小数点6位.double 可以精确到小数点13位

十进制转换二进制

整数转换二进制很简单,9 ==> 1001

小数部分怎么转换?
小数部分转换二进制方法为—>小数乘以2,取整,小数部分继续乘以2,取整,得到小数部分0为止,将整数顺序排列.

比如0.2
0.22 = 0.4,取整0,小数部分0.4
0.4
2 = 0.8,取整0,小数部分0.8
0.82 = 1.6,取整1,小数部分0.6
0.6
2 = 1.2,取整1,小数部分0.2
….无限循环
即为0011*N

简便办法(虽然并没有多简便),也可以用分数来
二进制 分数 十进制
0.1 1/(2的1次方1) 0.5
0.01 1/(2的1次方
0)+1/(2的2次方*1) ==> 1/4 0.25

转换二进制后又怎么存储

任何一个数的科学计数法表示都为(+/-)1.xxx*2^N,+/-表示符号位,尾数部分就可以表示为xxxx,指数部分用 N

  1. 整数部分第一位都是1,所以可以直接省略.
  2. 对于指数部分N,因为指数可正可负,8位的指数位能表示的指数范围就应该为:-127~128,所以可以对他进行置偏,根据浮点数的存储标注(IEEE),float 类型的指数起始数为127(二进制0111 1111),double类型的指数起始数为1023(二进制011 1111 1111),在此基础上加指数,得到的就是内存中的指数的表示形式.所以指数部分的存储采用移位存储,存储的数据为32位则元数据+127 ,64位则+1023
  3. 尾数则直接填入,位数不够则补0,位数过多则0舍1入

举个栗子🌰:

接之前:9.125 ==>1001.0012^0 ==> 1.0010012^3

9.125==>9==>1001
0.125==>
0.1252 = 0.25 ==> 取整0,余数0.25 ==>0
0.25
2 = 0.5 ==> 取整0,余数0.5 ==>0
0.5*2 = 1.0 ==> 取整1 ==>1

拼接后
9.125 ==> 1001.001==> 1.001001*2^3

1.001001*2^3 ==>
0 127 +3 0010 01 ==>
0 1000 0001 0010 01 ==>

所以内存里9.125实际存储的为 01000001000100100000000000000000 这一段二进制

再来看2.2,咱们倒着推:

2.2—>整数部分 10,小数部分看上面 知道了是0011N
10.00110011001100110011001100110011…
1.0 0011 0011…
2^1
0 1 0 0011 0011
0 127+1 0 0011 0011
0 128 0 0011 0011
0 1000 0000 0 0011 0011
0 1000 0000 00011 0011 0011 0011 0011 00—>小数部分23位
0 0111 1110 00110011001100110011000

精度丢失是什么

2.2转换成二进制为0 0111 1110 00110011001100110011 ….无穷,但是因为 float 和 double 存储的位数都有限,那么超出位数部分的肯定就被截断了.
这就导致2.2最终被保留下来的可能就是:
0 (1位符号位) 0111 1110(8位指数位) 00110011001100110011…(23位位数)

那么我们将这一段太监二进制再转换回十进制以后,他一定不完整了~ 符号位和指数位一定安全,但是尾数部分 00110011001100110011…(23位位数)肯定和 0011^N 代表的值不一样.
so, 精度丢失了.丢失在保存为二进制和转换回去的时候.

回头过来看代码

特殊数

NaN

上面提到的指数位为-127或者+128的时候,其实是保留了做特殊用途的.
关于这一系列特殊数,相当有意思.特别是 NaN(Not a Number)
可以看看这篇文章

总结

浮点数存储方式

要把小数装入计算机,总共分几步?你猜对了,3 步。

第一步:转换成二进制
第二步:用二进制科学计算法表示
第三步:表示成 IEEE 形式

在上面的第一步和第三步都有可能 丢失精度。

有浮点数可以不丢失精度么?

有,通常来说不丢失精度的小数最后一位都是5,但是位数是5的不一定不丢失.
为什么?可以按照之前的方法算一算就知道了

PS:任意进制转换-网站


原创文章,转载请注明地址: https://kevinmky.github.io