二级float和double引发的思考

举报
ReCclay 发表于 2022/02/22 01:48:18 2022/02/22
【摘要】 昨天一个要好的朋友问了我一个看似简单的C语言二级题,却让我语 塞,没法解释通。赶紧翻阅float、double以及%f的各个资料。其实也没 把查阅的东西和结果也没把自己说服,更别讲告诉朋友了。同时也深...

昨天一个要好的朋友问了我一个看似简单的C语言二级题,却让我语

塞,没法解释通。赶紧翻阅float、double以及%f的各个资料。其实也没

把查阅的东西和结果也没把自己说服,更别讲告诉朋友了。同时也深深觉

得自己对知识的一知半解。昨天的问题便是暴露了小数在计算机的存储以

及计算机大小端存储的盲点。其实自己是一知半解的!还好,还早,还能

继续学习,年轻真的好。。

不扯淡了,咱上菜。

问题

#include <stdio.h>
#include <stdlib.h>

int main()
{
    float fa;
    double db;
    fa = 11111.11111;
    db = 11111.111111111111111;
    printf("fa=%f\ndb=%f\n",fa, db);
    return 0;
}
  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

输出却有一下的结果:

fa = 11111.111328
db = 11111.111111
  
 
  • 1
  • 2

惊喜不?意外不?
真的惊喜,也真的意外!

迅速查了一波度娘的解释,看到讲

float的精度为7位,double的精度为15位。
(尾数决定了精度。23位的尾数7位二进制。51位的尾数,15位二进制)

照常理,是没错。float的输出就是11111.11 刚好不就是7位了吗?
对的,没错。但是最后是以%f输出的啊,%f是默认小数点后6为的!

所以问题又来了,1328又是从哪来的?并且不同机器似乎都是这个值,什么鬼?
这个。。。。。难住我了。

单从表面看,是没啥区别,但是我们又怎么会是肤浅的人嘞?我们追求的是内涵,追求的是本质!

好奇心驱动着,于是光荣的走进了debug的坑!(学会debug真的是学程序的一大利器啊)

debug之前必须先来补习一波计算机里面知识了!

在下面的探讨之前呢,还得掌握一个东西:计算机的大小端存储

浮点数的存储
概念:

任何数据在内存中都是以二进制的形式存储的,例如一个short型数据1156,其二进制表示形式为00000100 10000100。则在Intel CPU架构的系统中,存放方式为 10000100(低地址单元) 00000100(高地址单元),因为Intel CPU的架构是小端模式。但是对于浮点数在内存是如何存储的?目前所有的C/C++编译器都是采用IEEE所制定的标准浮点格式,即二进制科学表示法。

在二进制科学表示法中,S=M*2^N 主要由三部分构成:符号位+阶码(N)+尾数(M)。对于float型数据,其二进制有32位(也即是4个字节),其中符号位1位,阶码8位,尾数23位;对于double型数据,其二进制为64位,符号位1位,阶码11位,尾数52位。

            31        30-23       22-0

  
 
  • 1

float 符号位 (1bit) 阶码 (8bit) 尾数(23bit)

            63        62-52       51-0

  
 
  • 1

double 符号位 (1bit) 阶码(11bit) 尾数(52bit)

符号位:0表示正,1表示负

阶码:这里阶码采用移码表示,对于float型数据其规定偏置量为
127,阶码有正有负,对于8位二进制,则其表示范围为-128-127,double型规定为1023,其表示范围为-1024-1023。比如对于float型数据,若阶码的真实值为2,则加上127后为129,其阶码表示形式为10000010

尾数:有效数字位,即部分二进制位(小数点后面的二进制位),因为规定M的整数部分恒为1,所以这个1就不进行存储了。

先来举个小例子吧,看了你便懂了!

float型数据125.5转换为标准浮点格式

125二进制表示形式为1111101,小数部分表示为二进制为
1,则125.5二进制表示为1111101.1,由于规定尾数的整数部分恒为1,则表示为1.1111011*2^6,阶码为6,加上127为133,则表示为10000101,而对于尾数将整数部分1去掉,为1111011,在其后面补0使其位数达到23位,则为11110110000000000000000

则其二进制表示形式为

0 10000101 11110110000000000000000,则在内存中存放方式为:

00000000 低地址

00000000

11111011

01000010 高地址

这样也能更直观的体现小端存储了吧?

而反过来若要根据二进制形式求算浮点数如

0 10000101 11110110000000000000000

由于符号为为0,则为正数。阶码为133-127=6,尾数为11110110000000000000000,则其真实尾数为1.1111011。所以其大小为

1.1111011*2^6,将小数点右移6位,得到1111101.1,而1111101的十进制为125,0.1的十进制为1*2^(-1)=0.5,所以其大小为125.5。

依据上面的解释,咱们再来回到最初的程序
fa = 11111.11111
对应的二进制小数位是:进制在线转换工具

10101101100111.000111000111000110110100011110000100001

变成科学计数法的形式

1.0101101100111000111000111000110110100011110000100001*2^13

所以阶码就是13了。因为是正数,符号位是0

尾数是23位,注意这个数尾数显然是超过23位的,我们舍去的时候,要看23位后面那一位是0还是1,如果是1还要进位,总结0舍1进。(这个也是本人实践总结,欢迎来辩

可以数一下,第24位恰好就是1,所以我们向前进一位!
尾数就变成,

进位前: .0101 1011 0011 1000 1110 001
进位后: .0101 1011 0011 1000 1110 010

所以这个浮点数在内存中的存储形式为

0100 0110 0010 1101 1001 1100 0111 0010

可以看到恰好4个字节,也把float是4个字节这个圈画圆了,一切那么吻合!!!

那么内存到底是不是这样存储的呢?
看下debug就知道了!!

这里写图片描述

这里写图片描述

我们只看从0x60ff0c开始的4个字节就是对应的float在内存的存储的值了,
依次是 72 9c 2d 46

再结合刚刚说的小端存储,对照下数据
0100 0110 0010 1101 1001 1100 0111 0010 (低位在右边)

72 9c 2d 46 (低地址在左边)
从最右边数

72第一个字节的十六进制
9c第二个字节的十六进制
2d第三个字节的十六进制
46第四个字节的十六进制

最后再把
0100 0110 0010 1101 1001 1100 0111 0010
转化成十进制小数,转化方法和上面讲的那个一样

最高位0表示是正数,
阶码为1000 1100 对应 十进制140 140-127 = 13 所以阶码为13
尾数1.010 1101 1001 1100 0111 0010
向右移13位
10101101100111.00 0111 0010
整数部分对应十进制11111
小数部分对应十进制0.111328125

结果不刚好就是
11111.111328

完美~~~~over!

文章来源: recclay.blog.csdn.net,作者:ReCclay,版权归原作者所有,如需转载,请联系作者。

原文链接:recclay.blog.csdn.net/article/details/77877994

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。