首先,看一段代码:
int main()
{
double d = 3.14;
float f = d;
if ((float)d == f) {
printf("hello");
}
if (d != f) {
printf(" world!");
}
}
这个程序的输出结果是: hello world!
然后你有没有产生了好奇???这是为啥,下面我就来具体讲一讲float在内存中的存储方式!!!
目前所有的计算机都支持一个被称为IEEE浮点的标准.题外话:电器气和电子工程师协会(IEEE
)是一个包括所有电子和计算机技术的专业团体.它出版刊物,举办会议,并且建立委员会来定义标准,内容涉及从电力传输到软件工程.另一个IEEE标准的例子是无线网络的802.11标准.
IEEE浮点标准用 V = ( − 1 ) S × 2 E × M V = (-1)^S \times 2^E\times M V=(−1)S×2E×M的形式来表示一个数:
-
符号(sign): s决定这数是负数还是正数(s = 0),而对于数值0的符号位解释作为特殊情况处理.
-
尾数(significand) : M是一个二进制小数,它的范围是1~2- ϵ \epsilon ϵ ,或者是0~1- ϵ \epsilon ϵ.
-
阶码(exponent): E的作用是对浮点数加权,这个权重是二的E次幂(可能是负数).将浮点数的位划分为是三个字段,分别对这些值进行编码:
-
一个单独的符号位s直接符号编码s.
-
k位的阶码字段 e x p = e k − 1 … … e 1 e 0 exp = e_{k-1} …… e_1e_0 exp=ek−1……e1e0编码E
-
n位小数字段 f r a c = f n − 1 … … f 1 f 0 frac = f_{n-1} …… f_1f_0 frac=fn−1……f1f0编码尾数M,但是编码出来的值也依赖于阶码字段是否等于0.
在单精度浮点格式(C语言中的float)中,s,exp和frac字段分别为1位,k=8位和n=23位,得到一个32位的表示。
说了这么多,你可能还是看得云里雾里,没关系!!!接下来,我们还是通过代码来看一看:
int main()
{
float f = 1;e
printf("%x\n", *(int *)&f);
}
输出结果: 3f800000
上图为1作为float类型的内存中的表示,所以它到底是怎么算的?
V
=
(
−
1
)
S
×
2
E
×
M
V = (-1)^S \times 2^E\times M
V=(−1)S×2E×M
- 其实这个是 E = e − B i a s E = e-Bias E=e−Bias,其中 e e e是无符号数,其位表示为 e k − 1 … … e 1 e 0 e_{k-1}……e_1e_0 ek−1……e1e0,而Bias是一个等于 2 k − 1 − 1 2_{k-1}-1 2k−1−1,单精度是127的偏置值。由此产生指数的取值范围,对于单精度是-126~127.
- 小数字段 f r a c frac frac被解释为描述小数值f, 其中 0 ≤ f < 1 0 \le f < 1 0≤f<1, 器二进制表示为 0. f n − 1 … … f 1 f 0 0.f_{n-1}……f_1f_0 0.fn−1……f1f0,也就是二进制小数点在最高有效位的左边。尾数定义 M = 1 + f M = 1 + f M=1+f. 有时,这种方式也叫隐含的以1开头表示,因为我们可以把 M M M看做一个二进制表达式为 1. f n − 1 f n − 2 … … f 0 1.f_{n-1}f_{n-2}……f_0 1.fn−1fn−2……f0的数字。既然我们总是能够调整阶码 E E E,使得尾数 M M M在范围 1 ≤ M < 2 1\le M < 2 1≤M<2之中(假设没有溢出),那么这种表示方法是一种轻松获得一个格外精度位的技巧。既然第一位总是等于1,那么我们就不需要显示地表示它。
说了这么多, V = ( − 1 ) S × 2 e − 127 × 1. f V = (-1)^S \times 2^{e-127}\times 1.f V=(−1)S×2e−127×1.f, 现在这个公式能看懂了吗???
- 在 ∣ f ∣ ≥ 1 \vert f\vert \ge 1 ∣f∣≥1范围内,小数部分由frac部分表示
- 在 ∣ f ∣ < 1 \vert f\vert < 1 ∣f∣<1范围内, 小数部分由exp部分表示
能看懂的话这部分基本就掌握了,如果还看不懂,就在自己的电脑上多输出一些float的值,在画一画,就会明白了。
留一个问题,float类型真的不能精确表示所有值,比如0.50(
1
2
\frac 12
21)
是不是觉得关于浮点数的问题都解决了,emmmm……
别急,又一朵乌云有轮罩即将来袭,哈哈哈哈,你觉得下面这个程序的输出结果将会是什么???
int main()
{
float f = 1;
printf("%d\n", f);
}
你觉得会是什么呢???哈哈哈哈,随机值
之所以会出现随机值,是因为计算机是有专门的浮点数寄存器的,但是printf中的参数%d计算机会默认的从一些通用寄存器中拿值,而不会从浮点寄存器中拿,所以会导致随机值现象的出现。
基本说完了float类型的存储,但是也并没有介绍关于对于不能精确表示的浮点的舍入,主要这部分我也不是很了解,之后如果了解了,再来补充吧。
下图是double双精度类型的内存存储示意图
下面回到这篇文章一开始的那个程序吧,能理解了吗
假如我将double 的= 0.5
,你觉得程序的输出结果又是什么???
输出结果是hello
最后留一个问题,你觉得在浮点数0能精确表示吗???
今天看到一个小伙伴写的float存储的文章,恍然大悟,我写的这篇文章更倾向于从概念方面解释,但小伙伴的文章确实从为什么方面解释了,写的很好,大家可以看看。https://blog.csdn.net/weixin_43812622/article/details/103113718