c-primer-plus深入解读系列-从二进制到误差:逐行拆解C语言浮点运算中的400 ...

打印 上一主题 下一主题

主题 1855|帖子 1855|积分 5575

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
媒介

小提示:阅读本篇内容,至少必要相识double和float的二进制表示规则。
书中的代码示比方下:
  1. #include <stdio.h>
  2. int main(void)
  3. {
  4.   float a,b;
  5.   b = 2.0e20 + 1.0;
  6.   a = b - 2.0e20;
  7.   printf("%f \n",a);
  8.   return 0;
  9. }
复制代码
我的测试环境如下所示,在该测试环境中,a 等于 4008175468544.000000。
  1. Linux version 5.15.0-134-generic (buildd@lcy02-amd64-092) (gcc (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0, GNU ld (GNU Binutils for Ubuntu) 2.34) #145~20.04.1-Ubuntu SMP Mon Feb 17 13:27:16 UTC 2025
复制代码
为什么会产生这样的结果呢?由于书中的解释不够详细,以是我在阅读至此处时,天然而然产生了想要深入研究的想法,遂将研究结果写为本篇博客,供大家参考。
针对这个问题,我的思路是,逐行分析每一句代码都产生了什么结果,大概就可得知误差出现在那里。下面开始逐行分析。
逐行分析解读-第一行代码

起首是第一行:
  1. b = 2.0e20 + 1.0;
复制代码
先从右边的盘算 2.0e20 + 1.0 开始,我们起首必要把这两个常量的二进制表示出来,然后做浮点数加法运算。
2.0e20 和 1.0的二进制表示

起首,2.0e20会被当作double类型的数据举行处理,根据double类型的存储规则,它的底层二进制如下:
0-10001000010-0101101011110001110101111000101101011000110001000000
其中:

  • 符号位:0
  • 指数部门:10001000010
  • 尾数部门:0101101011110001110101111000101101011000110001000000
插播一个小知识(不关心的可略过):给定一个大数,它的二进制是怎样盘算得出的呢?

  • 起首,将\(2 \times 10^{20}\)举行质因式分解:
\(2 \times 10^{20} = 2 \times (2 \times 5)^{20} = 2^{21} \times 5^{20}\)
这表明,\(2 \times 10^{20}\)是由\(2^{21}\)和\(5^{20}\)的乘积构成,也就是说,这个大数的二进制形式可以看成5²⁰的二进制左移21位(即乘以2²¹),也就是说,我们必要求得\(5^{20}\)的二进制。

  • 求\(5^{20}\)的二进制:
\(5^{20}\)的十进制为:95367431640625
十进制求得二进制的过程,简单来说,即用95367431640625不断除以2,得到余数1或者余数0的一个竖向序列,将其倒序便是所求二进制。这里我直接写出二进制:
10101101011110001110101111000101101011000110001(一共为47位)

  • 整体二进制表示:
由上述盘算可得知,\(2 \times 10^{20}\)的二进制为:
\(10101101011110001110101111000101101011000110001 \times 2^{21}\) 我们将其规范化,得到
\(1.0101101011110001110101111000101101011000110001 \times 2^{67}\)

  • 求出了整体的二进制,double类型的具体存储就简单很多了。
起首是指数部门:67 + 1023 = 1090,二进制为10001000010
然后是尾数部门:直接提取小数点之后的部门,只有46位,还必要填充6个0即可(满意尾数52位的需求)
(小知识后咱们继续)其次,1.0也会被当作double类型来接收,底层二进制如下:
0 01111111111 0000000000000000000000000000000000000000000000000000
这个二进制就不详细阐明了,相信有了前文的基础,读者可以轻松盘算出来。
这里提供一套工具函数,用来打印float和double类型的二进制bit,用来验证很方便:
[code]#include #include void print_bits_float(float f){    union {        float a;        uint32_t b;    }c;    c.a = f;    for (int i = 0; i < 32; i++) {        printf("%d", c.b & 1  more than 31 is undefined behaviour.        printf("%d", c.b & (1 ULL
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

篮之新喜

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表