参考链接:[1] https://www.cnblogs.com/Xiao_bird/archive/2010/03/26/1696908.html
[2] https://blog.csdn.net/weixin_41042404/article/details/81275428###
导入
《算法竞赛入门经典(第2版)》P2:
printf("%.1f\n",8.0/5.0);
这句代码要注意在%后面有一个小数点,%.(x)f中的x表示的是浮点数小数点后的保留位数,而如果是%(x)f是默认小数点后保留6位。
浮点数的保留方式
在探究浮点数的保留方式时,笔者发现了这样的问题:
在保留位的下一位不是5的时候,遵循四舍五入的原则,但是当保留位的下一位为5时,保留规则并不遵循四舍五入。
printf("%.1f",1.65/3.0);//答案为0.5(实际值为0.55,相当于退位)
printf("%.1f",1.95/3.0);//答案为0.7(实际值为0.65,相当于进位)
笔者认为,浮点数的保留还应该是遵循四舍五入原则的
printf("%.18lf",1.65/3.0);//0.549999999999999933
printf("%.18lf",1.95/3.0);//0.650000000000000022
因为二进制并不能完全准确地表示浮点数,会出现一定的误差,所以会出现退位的情况。
浮点数的二进制表示
书中有一个实验6:
实验6:字符串%.1f不变,把8.0/5.0改成原来的8/5,结果如何?
笔者起初认为是8/5计算结果为1,输出%.1f为1.0,运行后发现结果是0.0。
将8替换为更大的整数,在一定范围内,运行结果都为0.0。
这涉及到浮点数二进制编码的问题。
float 32位(4字节) double 64位(8字节)
一个二进制小数由三部分组成:符号位(S)、指数位(E)、有效数字(M)。
数据类型 | float | double |
---|---|---|
符号位(S):(-1)S s=0 正数 s=1 负数 | 1位 | 1位 |
指数位(E) | 8位 | 11位 |
有效数字(M) | 23位 | 52位 |
对于M和E的特殊规定
-
有效数字(M):
1 ≤ \le ≤M<2
IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保留后面的部分
-
指数位(E):
E作为一个无符号整数,E为8位,取值范围为0~255,若为11位,取值范围为0~2047,但是科学计数法是可以出现负数的,所以IEEE 754规定,E的真实值必须再减去一个中间数,对于8位的E,是127,对于11位的E,是1023.
指数E还需要分为以下三种情况:
(1)E不全为0或不全为1。这时浮点数就采用上面的规则表示,即指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第一位的1。
(2)E全为0。这时,有效数字M不再加上第一位的1,所以浮点数的指数E等于1-127(或1-1023)。这样做是为了表示±0,以及接近0的很小的数字。
(3)E全为1。这时,如果有效数字M全为0,表示± ∞ \infty ∞,如果有效数字不全为0,表示这不是一个数(NaN)
对于一个十进制小数,变为二进制小数:
-
整数部分正常化为二进制整数
-
小数部分乘2取整
例:1.25 整数部分为1
小数部分0.25$ 2 = 0.50.5 2=0.5 0.5 2=0.50.5$2=1
所以 1.25(10)=1.01(2)
下面以520.25为例,化为二进制:
520.25(10)=1000001000.01
位置 | float | double |
---|---|---|
符号位S | 0 | 0 |
指数位E | 10001000 | 10000001000 |
有效数字M | 00000100001(后面加12个0) | 00000100001(后面加41个0) |
将S、E、M连在一起即为520.25的二进制.
为了解决上面实验6的问题,还需要解决一个笔者在试验中发现的问题:
printf格式串中的%f到底是float还是double?
在这里,笔者找到了一位博主的验证方法。
实验一,检查%f需要读取几个字节
int a=0,b=0,c=5;
printf("%f,%d\n",a,b,c);
输出结果:0,5
结论:%f读取8个字节,即两个整型大小
实验二,检查%lf需要读取几个字节
int a=0,b=0,c=5;
printf("%lf,%d\n",a,b,c);
输出结果:0,5
结论:%lf也读取8个字节(也许和机器位宽有关,我是32位的机器)
实验三,检查printf读取float类型数据
float a=0.0f;
int b=5;
printf("%f,%d\n", a, b);
输出结果:0.0,5
结论:float类型只占4个字节的数据,但前面实验一已经证明%f会读8个字节,即double类型的宽度,所以,编译器在将float类型参数入栈的时候,事先转换成了double类型。
实验四,再次证明实验三的结论
float a=0.0f;
int b=5;
printf("%d,%d,%d\n", a, b);
输出结果:0,0,5
结论:a在入栈的时候,占了8个字节。
**总结:(1)%f是按double类型输出 **
(2)float类型在作为参数进行传递的时候,编译器会先将它转换成double类型。
问题解释
至此我们可以解决上面实验6的问题了:
首先,我们用520.25进行实验
我们先将520.25的二进制(double),按long long int类型转换为整数
4647787383213785088
#include<stdio.h>
int main()
{
printf("%f",4647787383213785088);
}
答案为520.250000,证明我们的猜想是正确的!
对实验6的解释:
8/5首先按照整型进行计算为1.
然后将1化为二进制是0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001
将这个数表示为二进制double型的小数x:
x=(-1)0×2-1023×2-52=1×2-1075
是一个极为接近0的小数,所以会显示0.0