浮点数的二进制表示和printf()函数探究

参考链接:[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)。

数据类型floatdouble
符号位(S):(-1)S s=0 正数 s=1 负数1位1位
指数位(E)8位11位
有效数字(M)23位52位
对于M和E的特殊规定
  1. 有效数字(M):

    1 ≤ \le M<2

    IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保留后面的部分

  2. 指数位(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)

对于一个十进制小数,变为二进制小数:

  1. 整数部分正常化为二进制整数

  2. 小数部分乘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

位置floatdouble
符号位S00
指数位E1000100010000001000
有效数字M00000100001(后面加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

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值