sunrise085 发表于 2020-4-2 13:27:33

C语言中浮点数存储与%f输出细节探究

本帖最后由 sunrise085 于 2020-9-20 13:03 编辑

C语言中浮点数存储与%f输出细节探究
最近两天看到了几个帖子都在询问浮点数存储相关的问题,还有就是浮点数以%d输出那个数到底是怎么得到的,等等相关问题。
对此进行了以下总结,可能还有不对的地方,和大家相互探讨~~
第一部分、浮点数的存储型式

浮点数在计算机中的存储是有固定格式的,计算机中浮点数大多是以IEEE754的格式存放。IEEE754格式,将存储空间若干位分为三部分,分别是符号位、阶码、尾数,其中阶码和尾数的位数与浮点数本身有关。请看下表。
浮点数位数 符号位 阶码 尾数
32位 1位(最高位) 8位23位
64位1位(最高位) 11位52位
符号位不必说,0表示正,1表示负,浮点数的存放,先将浮点数转为1.n*2^m这种形式,其中m为阶码数,n为尾数。下面我们看一下3.141600的32位存储(为啥选3.1416?最近看到的那个帖子用的就是这个数,回答完他之后,我也没再换其他的数)首先,最高位符号位为0

然后计算阶码和尾数,3.141600=1.5708*2^1

阶码数是1,尾数是0.5708

阶码数转为阶码是1+127=128二进制为1000 0000

尾数0.5708转成23位尾数是100 1001 0000 1111 1111 1001(尾数转二进制的方法很简单,不断的乘2,,每乘一次2 ,就写一位,乘的结果没超过1就写0,超过1,就写1并减去1,一直乘到够自己用,或者没有小数为止)

      这里我们就得到了3.1416存储的这32位数 0100 0000 0100 1001 0000 1111 1111 1001 。十进制就是   1078530041   ,十六进制就是0X40490FF9

      是不是用printf格式化%d输出3.141600就是1078530041呢?你可以试一下~


结果实际上是776530087 。它是个什么鬼?
问题来了。这个数字是怎么来的?带着这个问题,我们看下一部分的内容。
这里有个小彩蛋~
**** Hidden Message *****
第二部分、printf格式化%f输出



现在回答上面的问题。这个问题是C语言的存储与printf输出之间的差异造成的。printf格式化输出,%f既可以输出单精度(32位)也可以输出双精度(64位),所以不管是单精度还是双精度,在输出之前都会被转成双进度。

3.141600转成双进度存储格式为 01000000 00001001 00100001 11111111 00101110 01001000 11101000 10100111。 1位符号位(黑色),11位阶码(棕色),52位尾数(绿色和紫色)(可以看一下,这个52位尾数的前23位(绿色)和上面的那个23为尾数是一样的的)。然后以%d输出实际上输出的是它的前32位,注意,所谓的前32位不是上面这一串的前32位,计算机中小端存储,低位在前,高位在后,所以输出的实际上是那64位的后半部分。
第三部分、编程为战不为看,拿例程说话



这里我们用union看一下他们的存储。
#include <stdio.h>
union Data{
      float a;
      short b;
      char c;
      int d;
};
union Num{
    double lf;
    int d;
    char ch;
};
int main()
{
    union Data aaa;
    aaa.a=3.141600;
    int i=0;
    printf("float类型的3.141600存储,占4个字节\n");
    printf("a=%f\n两个双字节以16进制显示:",aaa.a);
    for(i=0;i<2;i++)
      printf("b[%d]=%x",i,aaa.b);
    printf("\n两个双字节以10进制显示:");
    for(i=0;i<2;i++)
      printf("b[%d]=%d   ",i,aaa.b);
    printf("\n四个单字节:");
    for(i=0;i<4;i++)
      printf("c[%d]=%d   ",i,aaa.c);
      printf("\n一个四字节以16进制显示:d=%x\n",aaa.d);
    union Num num;
    num.lf=3.141600;
    printf("\ndouble类型的3.141600存储,占8个字节\n");
    printf("f=%f\n两个四字节以16进制显示:",num.lf);
    for(i=0;i<2;i++)
      printf("d[%d]=%x\t",i,num.d);
    printf("\n两个四字节以10进制显示:");
    for(i=0;i<2;i++)
      printf("d[%d]=%d   ",i,num.d);
    printf("\n八个单字节:");
    for(i=0;i<8;i++)
      printf("ch[%d]=%d   ",i,num.ch);
    return 0;
}

结果:
float类型的3.141600存储,占4个字节
a=3.141600
两个双字节以16进制显示:b=ff9b=4049
两个双字节以10进制显示:b=4089   b=16457      
四个单字节:c=-7   c=15   c=73   c=64      
一个四字节以16进制显示:d=40490ff9

double类型的3.141600存储,占8个字节
f=3.141600
两个四字节以16进制显示:d=2e48e8a7   d=400921ff      
两个四字节以10进制显示:d=776530087   d=1074340351      
八个单字节:ch=-89   ch=-24   ch=72   ch=46   ch=-1   ch=33   ch=9   ch=64



永恒的蓝色梦想 发表于 2020-4-2 13:36:38

膜拜大佬~赞一个!{:10_323:}

grant1944 发表于 2020-6-11 16:19:24

感谢大佬解答

RichardMin 发表于 2020-9-3 11:37:38

LZ您好,我想请教一下,如果是浮点数通过等号赋值给整型数据,编译器会进行截断处理,那么这个操作应该就和以%d的操作不一样了,这个截断处理的具体过程是什么呢?

sunrise085 发表于 2020-9-3 11:50:50

RichardMin 发表于 2020-9-3 11:37
LZ您好,我想请教一下,如果是浮点数通过等号赋值给整型数据,编译器会进行截断处理,那么这个操作应该就和 ...

对滴。float数值赋值给int类型变量,那种截断直接是数字的截断,实际操作是强制类型转换,不涉及到存储。
printf输出的时候,是直接根据格式化字符去读取的内存内容

风过无痕1989 发表于 2020-9-20 00:47:03

好帖是要顶上去的!

乐乐学编程 发表于 2020-9-20 10:16:56

这个帖子应该是精华帖子呀,版主呢?

qwenie123 发表于 2020-9-26 09:20:26

RE: C语言中浮点数存储与%f输出细节探究 [修改]

123lll 发表于 2020-9-29 09:18:36

您好,可以加qq交流一下吗671096314

乐乐学编程 发表于 2020-10-2 02:18:42

好帖子要多顶

木山二 发表于 2020-10-17 01:03:14

nice

瀚海导与练 发表于 2021-8-1 01:12:51


孤勇者23号 发表于 2022-8-10 12:28:51

1
页: [1]
查看完整版本: C语言中浮点数存储与%f输出细节探究