为什么float类型30000000.0f+3.0f的结果仍然是30000000.0f

本文深入解析浮点数加法运算中出现的精度丢失问题,通过实例解释大数吃小数现象,揭示IEEE-754标准下浮点数表示原理及加法运算过程,帮助理解计算机内部浮点数处理机制。

1. 首先看问题结果

是不是结果还是 30000000.0f 而不是 30000003.0f

 

2. 分析原因

这是一个典型的大数吃小数问题,原因需要从浮点数的计算机实现说起

对于float,是4字节(强调下我这里讲的都是32位浮点数,其它位思想同样适用),共32个bit,那么怎么用这32位来表示float

首先最容易想到的是  浮点数的每位十进制数用4bit来表示,比如 999999.99 这样每个9用1001表示,那么在32bit上的布局是下面这样:  100110011001100110011001.10011001   这样的表示数据范围是 000000.00 ~ 999999.99 共大概1亿个数字,这种把小数点固定的方法就是 “定点小数”,这样表达简单易懂,但是有个缺点就是,明明32bit可以最多表示42亿个数字,现在只能表示1亿,太浪费了。

 

还有一个就是 “浮点小数” ,顾名思义,就是小数点会浮动,比如 99.5 你可以表示成 9.995 x 10^1,也可以

表示成0.9995 x 10^2, 小数点可以浮动必然需要引入指数,这就是我们学数学的时候说的科学计数法,但是这是10进制,二进制数同样适用,只是科学计数法的底数是2而已。对于浮点数的二进制表示 常规的是 IEEE-754标准(强调下我下面讲都是IEEE-754标准),回头我打算再写一篇博客专门介绍一下这个标准(可以到时候留意下我的博客更新),现在我们只需要知道,这套标准定义下的浮点数是把32位bit位,划分为3部分:

对于 30000000.0f,二进制浮点数表示如下: 

A = 0 10010111 11001001110000111000000  (依次是符号位1位  指数位8位  数字位23位)

对于 3.0f,二进制浮点数表示如下: 

B = 0 10000000 10000000000000000000000  (依次是符号位1位  指数位8位  数字位23位)

转换工具可参考 : https://www.h-schmidt.net/FloatConverter/IEEE754.html

 

现在就是 A + B  ,其中A和B的指数位和数字位均不同,CPU此时会把 A和B的指数位统一(向大数A的指数靠拢),这样只要 加 A和B的数字位就可以了,A和B的指数位相差23位,如果B的指数位要跟A一致,那么B的数字位就要整体向右移动23位,正好把B的数字部分

10000000000000000000000   第一个1溢出了,这就是为什么  30000000.0f + 3.0f 结果还是 30000000.0f的原因。

 

特别提醒下,IEEE754 的指数位表示的不是真实的指数值,而是有个映射关系:1~254(bit表示值,0和255有另外的作用) 映射到 -126~127(指数表示的真实值) 这 254 个有正有负的数上。如果我们把 32bit划分的3个部分分别表示为 s(符号位),e(指数位),f(数字位),那么没有参与映射的0和255有如下的作用:

下篇我在讲IEEE754标准的博客里会把这个也交代清楚

 

 

### 浮点数运算 在C语言里,浮点数运算包含、减、乘、除等操作。当整数和浮点数进行运算时,整数会转换为浮点数类型,这属于隐式类型转换。例如代码中的`float f1 = 2011.0f / 3.0f; double d1 = 2011.0 / 3.0; double d2 = 2011.0f / 3.0;`,都是浮点数的除法运算。在`2011.0f / 3.0f`中,两个操作数都是`float`类型结果也是`float`类型;`2011.0 / 3.0`中,两个操作数为`double`类型结果是`double`类型;`2011.0f / 3.0`里,`2011.0f`为`float`类型,`3.0`是`double`类型,`2011.0f`会先转换为`double`类型再进行运算,结果为`double`类型[^2]。 ### 浮点数精度 浮点数在计算机中是以二进制形式存储的,这会导致部分十进制小数无法精确表示。`float`类型通常占用32位,精度约为6 - 7位有效数字;`double`类型占用64位,精度约为15 - 16位有效数字。在代码中,`float f1 = 2011.0f / 3.0f;`由于`float`精度有限,可能无法精确表示`2011.0 / 3.0`的结果;而`double d1 = 2011.0 / 3.0;`和`double d2 = 2011.0f / 3.0;`使用`double`类型,精度相对较高,但同样可能存在精度损失。 ### 浮点数比较 因为浮点数存在精度问题,所以不能直接使用`==`来比较两个浮点数是否相等,而应判断它们的差值是否在一个极小的范围内。以下是示例代码: ```c #include <stdio.h> #include <math.h> #define EPSILON 1e-9 int main() { float f1 = 2011.0f / 3.0f; double d1 = 2011.0 / 3.0; double d2 = 2011.0f / 3.0; // 比较 f1 和 d1 if (fabs((double)f1 - d1) < EPSILON) { printf("f1 and d1 are equal within tolerance.\n"); } else { printf("f1 and d1 are not equal within tolerance.\n"); } // 比较 d1 和 d2 if (fabs(d1 - d2) < EPSILON) { printf("d1 and d2 are equal within tolerance.\n"); } else { printf("d1 and d2 are not equal within tolerance.\n"); } return 0; } ``` 在上述代码中,定义了一个极小的常量`EPSILON`,通过`fabs`函数计算两个浮点数的差值的绝对值,若该值小于`EPSILON`,则认为两个浮点数相等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值