浮点数学习笔记
前言
最近在工作中遇到汇率换算的问题,需要将用户订单中的的本币换算成美元,用 golang
写了个脚本进行换算,当把汇率变量的类型定义为 float32
的时候,计算结果存在不能接受的误差
查看了一下 golang
的官方文档,发现 golang
实现的浮点型数据是基于 IEEE 754标准
,这一标准在存储小数的时候先天就 存在误差
,下面会一一介绍。
float
精度问题
先来个小例子,抛出问题
1 | import "fmt" |
程序执行结果:
this is a float32 64.349998
this is a float64 64.350000
可以看到, 使用 float32
类型的时候, 小数 64.35
存储的并不精准;不难想象,对 float32
进行逻辑计算的时候肯定会产生误差;那么使用 float64
类型数据的时候,计算结果就一定精准吗?答案显然是否定
的,下面给将给出答案
为什么叫 浮点数
为什么叫 浮点数
, 浮点数
这个名词是相对 定点数
来说的,从这两个名词中可以看出,这两个概念的差别就在于 点
, 这里的 点
指的是小数中的 小数点
;
大家都知道,计算机都是使用 二进制
的形式来存储和计算数据的,对于小数的处理也是如此;
存储小数的时候,计算机将小数分为 整数
和 小数
两个部分进行处理:
定点数
就是将小数点的位置固定,分别分配固定的位数用于存储 整数
和 小数
部分,
例如,我们用 32bit 存储小数,第31位存储符号,23~30位存储 整数
,0~22 位存储小数,如下图,
0 | 0000 1111 | 0100 0000 0000 0000 0000 000 | 15.25 |
---|---|---|---|
31bit符号位 | 23~30bit保存整数部分 | 0~22bit保存小数部分 | 十进制小数 |
上面这个例子中 定点数
将小数点固定在 22bit 和 23bit 之间;可以很明显的看出,这种存储方式受到位数的限制,能表示的数字范围很小,上例中小数的范围就是 -255.xxx ~ 255.xxx
(ps:寡人太懒了,不想算[1/2 + 1/4 + … + 1/2-23])
也正是这个原因,计算机放弃了这种方式,采用了 浮点数
的方式。
浮点数
从名称上来解释的就是,小数点的位置是浮动的;简单来说浮点数就是将一个数字用科学计数法
表示,先将数字分为 基数
、指数
; 再将基数
分为整数部分和小数部分,例如:
“12345 = 1.2345 x 104;当然这里是十进制,而计算机在存储浮点数
的时候当然还是二进制
“15.25 = 1111.01 = 1.11101 x 23
让我们用32bit 保存小数:
0 | 1000 0010 | 1110 1000 0000 0000 0000 000 | 15.25 |
---|---|---|---|
31bit符号位 | 23~30bit保存指数部分 | 0~22bit保存小数部分 | 十进制小数 |
你能从这个二进制中看出小数点的位置吗?
这里大家要注意: 浮点数不仅仅可以保存小数!整数也是可以的,但是用浮点数表示整数这种行为不鼓励,毕竟浮点数表示数字是不精确的
IEEE 754
存储 浮点数
上面讲浮点数
的时候也基本介绍了 IEEE 754
标准IEEE 754
标准依赖于 科学计数法
,将一个数字用二进制科学计数法表示,将一个数字分为指数
和基数
,用一位表示符号位,再将基数分为整数(二进制的整数部分当然是1了)和小数部分;IEEE 754
规定了四种表示浮点数值的方式:单精确度(32位)、双精确度(64位)、延伸单精确度(43比特以上,很少使用)与延伸双精确度(79比特以上,通常以80位实现);如下:
表示方式 | 符号位(Sign,S) | 指数部分(Exponent,E) | 小数部分(Fraction,F) |
---|---|---|---|
单精确度(32位) | 1bit | 8bit | 23bit |
双精确度(64位) | 1bit | 11bit | 52bit |
一个单精确度(32位)浮点数的表达公式如下:
x=(-1)S×(1.F)×2E-127 e=E-127
Sign
: 简写为S,符号位,很简单就是 1:负数 0:正数Exponent
: 简写为E,指数部分,计算公式是:指数+接码偏移量(127)
Fraction
: 简写为F,小数部分
到这里大家就很自然的会想到一个问题 阶码偏移量为何用127?
这个问题也困扰我很久,看了维基百科
等资料,最后在知乎
上找到了一个比较靠谱的答案,引用如下:
主要是为了让表示的范围能够对称起来
这个算一算就清楚了。当阶码E 为全0且尾数M 也为全0时,表示的真值x 为零,结合符号位S 为0或1,有正零和负零之分。当阶码E 为全1且尾数M为全0时,表示的真值x 为无穷大,结合符号位S 为0或1,也有+∞和-∞之分。这样在32位浮点数表示中,要除去E,用全0和全1(255)10表示零和无穷大的特殊情况,指数的偏移值不选128(10000000),而127(01111111)。对于规格化浮点数,阶码E范围是1~254。 分两种情况计算如下: 1)偏移值为127时,绝对值范围大致是:1.210^(-38)~3.410^(+38); 2)如果偏移值取为128时, 绝对值范围大致是:5.910^(-39)~1.710^(+38); 可见偏移值取127时,上下范围基本对称,相对合理点。
作者:yuanyuany
链接:https://www.zhihu.com/question/24784136/answer/144601879
来源:知乎
如果是双精确度(64位)
十进制
和 IEEE 754浮点数
相互转化
下面来个小例子, 我们将 64.35
转化成IEEE 754浮点数
“1. 现将 64.35
用转化成 二进制
先看整数部分 64
= 0100 0000
再来转化小数部分转二进制
0.35x2 = 0.7 0 // 取计算结果整数部分
0.70x2 = 1.4 1 // 取上一计算结果的小数部分乘以2
0.40x2 = 0.8 0
0.80x2 = 1.6 1
0.60x2 = 1.2 1
0.20x2 = 0.4 0
0.40x2 = 0.8 0
0.80x2 = 1.6 1
0.60x2 = 1.2 1
0.40x2 = 0.8 0 // 到这里已经是循环重复了,这里永远不会算出1.0,所以用 IEEE 754
表示的时候是无限循环
小数部分: 0.35
= 01 0110 0110 ...
二进制结果 64.35
= 1000000.0101100110...
= 1.0000 0001 0110 0110 0110… x 26
用 32bit IEEE 754
存储 64.35
s = 0
expr = 127 + 6 = 133 = 1000 0101
frag = 0000 0001 0110 0110 0110 011
IEEE 754
32位存储 64.35
的情况如下64.35
= 0 | 1000 0101 | 0000 0001 0110 0110 0110 011
用 64bit IEEE 754
存储 64.35
s = 0
expr = 1023 + 6 = 1029 = 1000 0000 0101
frag = 0000 0001 0110 0110 0110 0110 0110 0110 0110 0110 0110 0110 0110
IEEE 754
64位存储 64.35
的情况如下64.35
= 0 | 1000 0000 0101 | 0000 0001 0110 0110 0110 0110 0110 0110 0110 0110 0110 0110 0110
我们再将IEEE 754浮点数
还原成小数,
先看看32bit 的情况
看看,算出指数 1000 0101 - 1111111 = 133 - 127 = 6
然后这个浮点数的二进制表示就是 1.0000 0001 0110 0110 0110 011 x 2<sup>6</sup>
下面就是换算成十进制
“1.0000 0001 0110 0110 0110 011 x 26 = 100 0000.0101 1001 1001 1001 1
先看整数部分 0100 0000 = 64
在看小数部分 0.0101 1001 1001 1001 1 转十进制
“1/22 + 1/24 + 1/25 + 1/28 + 1/29 + 1/212 + 1/213 + 1/216 + 1/217 = 0.349052429199219
结论: 0 | 1000 0101 | 0000 0001 0110 0110 0110 011
= 64.349052429199219
再看 64bit 的情况:
“1.0000 0001 0110 0110 0110 0110 0110 0110 0110 0110 0110 0110 0110 x 26 = 100 0000.0101 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 10
0.0101 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 10 =
“1/22 + 1/24 + 1/25 + 1/28 + 1/29 + 1/212 + 1/213 + 1/216 + 1/217 + 1/220 + 1/221 + 1/224 + 1/225 + 1/228 + 1/229 + 1/232 + 1/233 + 1/236 + 1/237 + 1/240 + 1/241 + 1/244 + 1/245 = 0.349999999999994
结论: 0 | 1000 0000 0101 | 0000 0001 0110 0110 0110 0110 0110 0110 0110 0110 0110 0110 0110
= 64.349999999999994
从这里也可以看出浮点数都不精确!当对精确有要求的时候尽量避免使用浮点数。