前言
float类型,在很多地方没有看明白,多文字也看得人心烦。
最近不是很愿意看多文字的博客和技术文章了,但确实不是什么好事。
要改。
本文用大量图片讲解了浮点数在计算机中的存储方式以及浮点数的最值、精度等问题,文末有测试用的C++程序。
从二进制表示小数说起
我们先不让小数点“浮动”,表示12.625这个数字。
小数点左边好说,直接十进制转二进制就好了:
12->1100
小数点右边呢?
其实和十进制一样,二进制的小数点左边从左到右分别表示的是23、22、21、20,现在跳到小数点右边,自然也就有了2-1、2-2、2-3……这和我们早就熟悉了的十进制小数也是一样的思想,只不过二进制只有0、1两种数字。
再到科学计数法
十进制的科学计数法是人人都会了:
12.625
(
常
规
表
示
)
=
1.2625
×
1
0
1
(
常
规
科
学
计
数
法
)
=
1.2625
E
1
(
源
码
中
的
表
示
法
)
12.625(常规表示)=1.2625\times10^{1}(常规科学计数法)=1.2625E1(源码中的表示法)
12.625(常规表示)=1.2625×101(常规科学计数法)=1.2625E1(源码中的表示法)
然而计算机很憨,不认识十进制,只能存储二进制,二进制1100.101
的科学计数法如何表示呢?
把十进制的思想直接用上就好,只不过底数要从10改成2:
浮点数在计算机中的存储方式
顺着讲下来,想必也能看出来浮点数在二进制中是通过二进制的科学计数法存储的了,我们需要的有这么三部分:
- 符号。表示浮点数的正负号,1bit足够;
- 数字部分(尾数部分)。即上图中蓝色的部分,因为除了0以外,蓝色数字部分小数点左边的首位都是1,所以我们直接存储小数点后面的部分,即尾数部分(本例为100101)。
- 指数部分。即上图中红色的部分,是一个有符号整数(本例为3)。
我们以4字节的float为例:
-
符号位。占1bit,0表示正数,1表示负数;
-
指数部分。占后续的8bit,以+127偏移的方式存储,比如指数部分若为-3,则存储为130(10进制),即二进制的
1000 0010
;为什么指数部分以偏移的方式存储而非以二进制补码方式存储?
-
尾数部分。占后续的23bit,本例中为
100101
;
总结:记符号位为Sign
,指数部分为Exponent
,尾数部分为Fraction
,则浮点数为:
注:指数部分的0x00、0xff特殊值在下一节讲。
8字节的double双精度浮点数,符号位不变,指数部分变为11bit,尾数部分变为52bit,其他同理。
浮点数的特殊值和最值
本节回答这么几个问题:
- 浮点数最大(绝对值最大)可以表示到什么值?
- 浮点数如何处理越界的值?
- 浮点数如何表示0?
指数部分的特殊值: 0xff 和 0x00
浮点数标准规定1111 1111(0xff)
被留作特殊值用,对于越界的无法处理的值,也就是inf
或-inf
(infinity),尾数部分均为0。
对于指数部分为0xff
,尾数部分非0x00
,则留作NaN
,用于表达对负数开平方、对负数求对数、inf / inf这类无效数字。
然后是0x00
,因为之前讲的尾数部分默认是把小数点前一位看成1的,无法表示0,浮点数标准规定,当指数部分和尾数部分为全0时,浮点数表示0,由于符号位的存在,也就出现了±0这种东西,可以看做是相等的,但是某些情况会对其区分,这里我们不做讨论。
当指数部分为0x00,而尾数部分非0时,浮点数标准规定这样的浮点数为非规格化浮点数(subnormal number),之所以非规格化,是因为此时的浮点数精度并不能达到要求,用我的理解说:小数点就不再是浮动的了,不配叫浮点数,是不正常的浮点数。二进制科学计数是这样的:
(
−
1
)
s
i
g
n
×
0.
f
r
a
c
t
i
o
n
×
2
−
126
(-1)^{sign}\times0.fraction\times2^{-126}
(−1)sign×0.fraction×2−126
浮点数的最值
首先看正数,要找浮点数能正常表示的最大值,指数部分首先要尽量大,然而浮点数标准规定,指数部分最大只能到1111 1110(0xfe)
,这是由于1111 1111(0xff)
被留作特殊值用了,也就是inf
,对于越界的无法处理的值,会显示为inf
或-inf
,尾数部分均为0。这也就解决了第二个问题。
我们继续看最大值:指数部分最大可以取到1111 1110(0xfe)
即127,尾数部分最大可以取到23个1
,可以表示的最大数如下,十进制表示3.402823466e+38
,程序中可以用FLT_MAX
得到;
1.11111111111111111111111
×
2
127
(
正
数
f
l
o
a
t
可
以
取
到
的
最
大
值
)
1.111 1111 1111 1111 1111 1111\times2^{127}(正数float可以取到的最大值)
1.11111111111111111111111×2127(正数float可以取到的最大值)
不考虑负数的情况下,float也有可以表示的最小正数,而且分为规格化和非规格化的浮点数。
对于规格化的浮点数,指数部分尽量小,最小到0x01
,即-126,尾数部分也尽量小,此时取到最小值,十进制表示1.175494351e-38
,程序中可以用FLT_MIN
得到:
1.00000000000000000000000
×
2
−
126
(
规
格
化
正
数
f
l
o
a
t
可
以
取
到
的
最
小
值
)
1.000 0000 0000 0000 0000 0000\times2^{-126}(规格化正数float可以取到的最小值)
1.00000000000000000000000×2−126(规格化正数float可以取到的最小值)
然而在指数部分为0x00时,非规格化浮点数其实可以取到比FLT_MIN
更小的正数,十进制表示1.401298464e-45F
,程序中用FLT_TRUE_MIN
可得到:
0.00000000000000000000001
×
2
−
126
(
非
规
格
化
正
数
f
l
o
a
t
可
以
取
到
的
最
小
值
)
0.000 0000 0000 0000 0000 0001\times2^{-126}(非规格化正数float可以取到的最小值)
0.00000000000000000000001×2−126(非规格化正数float可以取到的最小值)
正数的最值有了,负数的最值只需要把符号位置1就好。
值得注意的是,浮点数的最值并不代表浮点数可以取到区间内每一个值,浮点数是有精度的,也就是下一节要提到的浮点数精度问题
浮点数精度问题
本节将回答以下问题:
-
为什么如下代码会输出False
double x = 0.0; // float也一样 for (int i = 0; i < 10; ++i) { x += 0.1; } double y = 1.0; // float也一样 cout << (x == y ? "True" : "False") << endl;
-
为什么如下代码又会输出True
bool isEqual(float a, float b) { return a == b; } int main() { double x = 0.0; for (int i = 0; i < 10; ++i) { x += 0.1; } double y = 1.0; cout << (isEqual(x, y) ? "True" : "False") << endl; return 0; }
-
我们应如何对浮点数的精度进行取值?文章来源:https://www.toymoban.com/news/detail-558803.html
赶一下五一创作勋章,后面的部分后续再补上。文章来源地址https://www.toymoban.com/news/detail-558803.html
测试用程序
// 打印二进制形式的float数,并将指数部分、尾数部分间隔开,稍微改一下就可以间隔打印double类型
template<class T>
void printbybit(const T &ob) {
char *p_e = (char *) &ob;
char *p = p_e + sizeof(T) - 1;
int cnt = 0;
for (; p >= p_e; p--) {
for (int i = 7; i >= 0; i--) {
if (cnt == 1 || cnt == 9) cout << ' '; // 打出间隔,方便查看各个部分值
cout << (((*p) & (1 << i)) ? 1 : 0);
cnt++;
}
}
cout << endl;
}
到了这里,关于【多图警告】彻底搞懂浮点数的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!