C语言中float的舍入

最近在学习C语言,是从《C Primer Plus》开始的。看到数据类型这一章,对于float类型的舍入有些疑问。

首先,先贴出书中关于float储值的一个描述。

C标准规定,float类型必须至少能表示6位有效数字,且取值范围至少是10的-37次方-10的正37次方。前一项规定了指float类型至少精确表示小数点后6位有效数字,如33.333333.后一项.......
#include <stdio.h>
int main(void)
{
        float a,b,c;
        c = 2.0e20;
        b = 2.0e20 + 1.0;
        a = b - 2.0e20;
        printf("%f %f %e\n", a, c, c); 
        return 0;
}

4008175468544.000000 200000004008175468544.000000 2.000000e+20

书中关于得出这个奇怪的结果是这样描述的:

计算机缺少足够的小数为来完成正确的运算。2.0e20是2后面的有20个0.如果把该数+1,那么发生变化的是第21位。要正确运算,程序至少要储存21位数字。而float类型的数字通常只能储存按照指数比例缩小或者放大6或者7位有效数字。

关于这段解释,我是一脸懵逼。

  • 2e20远小于float最小表示数的范围,+1怎么就不够了?
  • float正数小数分开储存,32位,8位表示指数,24位表示小数,这样+1也不会超过。

是我遗漏了什么地方吗?这确实很困扰我。

阅读 8k
2 个回答

简单说下吧,有时间再来补上。
以下结果基于gcc5.4

#include <stdio.h>

int main(void)
{
    float a,b;
    b = 2.0e20 + 1.0;
    a = b - 2.0e20;
    printf("%f\n", a);
}
---
4008175468544.000000

如果把1.0 换成1e2, 1e3...1e13,得到的结果都是一样的。

精度问题

float使用23位存尾数的,注意这23位存的是01串,是二进制的尾数。
由于2^23 = 8388608(只是简单计算,去看IEEE754知道还隐含了一个1),所以存储十进制的小数小数点后精度也就6、7位。

计算

浮点数的计算,见维基百科这里
可知,浮点数计算需要把指数统一然后计算,那么问题来了,无论是1.0统一到2.0e20还是2,0e20统一到1.0,尾数的精度都超过了上面的精度,所以就有了这种奇怪的结果。

这个奇怪结果的产生和类型也有关系,1.02.0e20都是double型,这意味着b = 2.0e20 + 1.0是先对两个double型字面量计算,然后再赋给float型变量b,精度丢失。

题主有兴趣的话,去看看IEEE754标准,以及Google一下浮点数的计算

  突然看到这个问题,忍不住想要回答一下,毕竟,程序设计就是一门数据和算法的学问。
  首先呢,浮点型的值一般是由科学计数法表示的,不论是float类型,double类型还是long double类型都是这样。而且存储它们的时候和整型数值不太一样,整数是直接储存数字所转换的二进制数的,浮点型也是转换为二进制数但是是转换存储尾数、指数以及它们的符号。下面就拿float类型来说吧。
  float类型一般字长32位,其中8位用来存储指数和它的符号,24位用来存储尾数和它的符号,你可以算一算这32位里面,就单单存指数和尾数而言,在不受机器系统影响的情况下,究竟最大可以存多大的浮点值,远远大于你例子中的数值,但是我们不能拿这个当范围,你例子里的2.0e20,你不能把它的存储当成int型数字存储方式,在存储这个数字的时候是分成尾数2.0和指数20然后分别存储的,并不会说把200000000000000000000存起来,你想想把它转化成2进制要占多少位?所以+1更是微不足道了,所以说没有足够的小数来存储它(小数在存储的时候可不是小数哦),至于你说的那个范围,那个范围可是用科学记数法表示的(其实用以2为底表示会更加科学),而且是C标准里边的最小范围(因为有点机器比较差,只是指定了最小范围),根据机器的种类,具体的范围是会变化的,不如,我们平常用的32位个人电脑,float类型的范围是3.04E-38~3.04E38,这个范围也是用科学记数法表示出来的。至于你说的会被截断的原因和依据,其实浮点默认用double类型是为了保持数值的精度,往往float的有效小数的位数满足不了我们对精度的要求,所以无论是默认,还是在计算过程中所用到的浮点值常量,我们都用double类型,因为double类型的精度更高,截断成float类型,只是为了保证变量的数据格式,但是截断会损失精度,而且不会四舍五入的,只是因为因为你的变量声明就是float型的所以要截断,如果你声明double类型就不会了。
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题