浮点数比较中的问题

7
void main()
{
    float f = 0.98;
    if(f <= 0.98)
        printf("hi");
    else
        printf("hello");
    getch();
}

我在这里遇到了一个问题。使用不同的浮点数值f,我得到了不同的结果。为什么会出现这种情况?

4
@valdo:没有进行更彻底分析的情况下,这通常是一个糟糕的建议。 - R.. GitHub STOP HELPING ICE
1
@KeithThompson: 不幸的是,你需要看一下在C和C++中main()应该返回什么? - Jonathan Leffler
@JonathanLeffler:啊!我不知道微软实际上将void main(void)记录为C标准5.1.2.2.1中指定的“其他实现定义方式”。我仍然认为在Microsoft C下,void main(void)是可以接受的,但void main()不行(这里没有足够的空间进行语言律师式的争论)。除非你正在使用独立实现,否则绝对没有理由定义具有void返回类型的main - Keith Thompson
1
@KeithThompson: 当我发现 MSVS 2008 及以后版本的文档记录时,我的心情和你听起来一样的沮丧。 我同意 int main(void) 更可取,但我很想听听为什么 int main() 作为函数定义不行的更详细解释,有兴趣者请邮件联系(邮箱在个人资料中)。我使用的 GCC 编译器选项意味着我使用 int main(void) 或者 int main(int argc, char **argv) 而不是 int main(),但这并不是一个语言律师的论点。 - Jonathan Leffler
1
@JonathanLeffler:简而言之,int main()不等同于int main(void)。后者会使main(42)成为约束违规;前者则不会。另一方面,在ANSI C之前,int main()肯定是正确的,并且意图是为了避免破坏旧代码,因此我认为应该允许使用int main()(但已过时),但在我看来,标准文本当前的措辞并没有这样说明。 - Keith Thompson
显示剩余3条评论
2个回答

22

f使用了float精度,但默认情况下0.98是double精度,因此语句f <= 0.98会使用double精度进行比较。

因此,在比较中f被转换为double,但结果可能略大于0.98。

使用

if(f <= 0.98f)
或者使用 double 代替 f
详细来说……假设 floatIEEE 单精度浮点数格式doubleIEEE 双精度浮点数格式
这些浮点数采用基于2的表示方式存储。在基于2的情况下,这个数字需要无限的精度才能表示,因为它是一个重复的小数:
0.98 = 0.1111101011100001010001111010111000010100011110101110000101000...

float只能存储24位有效数字,即

       0.111110101110000101000111_101...
                                 ^ round off here
   =   0.111110101110000101001000

   =   16441672 / 2^24

   =   0.98000001907...

一个 double 可以存储53位有效数字,所以

       0.11111010111000010100011110101110000101000111101011100_00101000...
                                                              ^ round off here
   =   0.11111010111000010100011110101110000101000111101011100

   =   8827055269646172 / 2^53

   =   0.97999999999999998224...

因此,在 float 中0.98会变得稍微更大,在 double 中会变得更小。


1
+1 好观点,也许比我的回答更接近他的问题。 - egrunin

3
由于浮点数值不是数字的精确表示,所以所有十进制数都需要在计算机上表示为二进制数。在这种转换过程中会丢失精度。
请在http://en.wikipedia.org/wiki/Floating_point阅读更多相关信息。

一个例子(来自我VB6时代遇到的问题)

要将数字1.1转换为单精度浮点数,我们需要将其转换为二进制。需要创建32位。

第1位是符号位(它是否为负[1]或正[0]) 位2-9用于指数值 位10-32用于尾数(也称为有效数字,基本上是科学计数法的系数)

因此,对于1.1,单精度浮点值存储如下(这是截断值,编译器可能会在幕后四舍五入最不重要的位,但我只是截断它,这样略微不太准确,但不会改变此示例的结果):

s --exp--- -------mantissa--------
0 01111111 00011001100110011001100

如果你注意到尾数中有重复的模式0011。在二进制中,1/10就像十进制中的1/3一样。它会一直持续下去。因此,为了从32位单精度浮点值中检索值,我们必须首先将指数和尾数转换为十进制数,以便我们可以使用它们。
符号= 0 = 正数
指数:01111111 = 127
尾数:00011001100110011001100 = 838860
对于尾数,我们需要将其转换为十进制值。原因是在二进制数之前有一个隐含的整数(即1.00011001100110011001100)。隐含的数字是因为尾数表示规范化值,用于科学计数法:1.0001100110011.... * 2^(x-127)。
为了从838860中获取十进制值,我们只需除以2^-23,因为尾数中有23个位。这给我们0.099999904632568359375。将隐含的1添加到尾数中,得到1.099999904632568359375。指数为127,但公式要求2^(x-127)。
所以这里是数学:

(1 + 099999904632568359375) * 2^(127-127)

(1 + 0.099999904632568359375) * 2^0

1.099999904632568359375 * 1 = 1.099999904632568359375

正如您所看到的,1.1实际上并没有以单精度浮点值1.1的形式存储。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接