提问人:Ashish 提问时间:12/3/2009 最后编辑:Bo PerssonAshish 更新时间:12/4/2022 访问量:10693
float 与 float 文字相比的奇怪输出
strange output in comparison of float with float literal
问:
float f = 0.7;
if( f == 0.7 )
printf("equal");
else
printf("not equal");
为什么输出?not equal
为什么会这样?
答:
发生这种情况是因为在您的陈述中
if(f == 0.7)
0.7 被视为双精度。尝试 0.7f 以确保将该值视为浮点数:
if(f == 0.7f)
但正如 Michael 在下面的评论中建议的那样,你永远不应该测试浮点值的完全相等性。
评论
这个答案是对现有答案的补充:请注意,0.7 不能完全表示为浮点数(或双精度)。如果它被精确地表示,那么在转换为浮点数然后返回双精度时不会丢失信息,您就不会有这个问题。
甚至可以说,对于无法精确表示的文字浮点常量,应该有一个编译器警告,特别是当标准对于舍入是在运行时以设置为该时间的模式还是在编译时以另一种舍入模式进行时非常模糊时。
所有可以精确表示的非整数都具有最后一个十进制数字。不幸的是,反之亦然:有些数字的最后一个十进制数字是无法准确表示的。小整数都可以精确表示,除以 2 的幂会将一个可以表示的数字转换为另一个可以表示的数字,只要您不进入非规范化数字的领域。5
5
评论
正如其他评论者所指出的那样,您面临的问题是,测试浮点数之间的精确等价通常是不安全的,因为计算中的初始化错误或舍入错误可能会引入细微的差异,从而导致 == 运算符返回 false。
更好的做法是做类似的事情
float f = 0.7;
if( fabs(f - 0.7) < FLT_EPSILON )
printf("equal");
else
printf("not equal");
假设FLT_EPSILON已定义为平台的适当小浮点值。
由于舍入或初始化误差不太可能超过 FLT_EPSILON 的值,因此这将为您提供所需的可靠等效性检验。
考虑一下:
int main()
{
float a = 0.7;
if(0.7 > a)
printf("Hi\n");
else
printf("Hello\n");
return 0;
}
如果 (0.7 > a),这里 a 是一个浮点变量,是一个双精度常数。double 常数大于浮点变量 a。因此,满足 if 条件并打印0.7
0.7
'Hi'
例:
int main()
{
float a=0.7;
printf("%.10f %.10f\n",0.7, a);
return 0;
}
输出:
0.7000000000 0.6999999881
首先,让我们看看浮点数的内部。我取 0.1f 它是 4 字节长 (binary32),十六进制是
3D CC CC CD。
通过标准IEEE 754将其转换为十进制,我们必须这样做:
在二进制 3D CC CC CD 中
,01111011 1001100 为 0 11001100 11001101
这里的第一个数字是符号位。0 表示 (-1)^0 表示我们的数字为正。
第二个 8 位是指数。在二进制中,它是01111011 - 十进制 123。但是真正的指数是 123-127(总是 127)=-4,这意味着我们需要将得到的数字乘以 2^ (-4)。
最后 23 个字节是 Significand 精度。在那里,第一位乘以 1/ (2^1) (0.5),第二位乘以 1/ (2^2) (0.25),依此类推。在这里我们得到:
我们需要将所有数字(2 的幂)相加并加到 1(始终为 1,通过标准)。它是
1,60000002384185791015625
现在让我们将这个数字乘以 2^ (-4),它来自指数。我们只是将上面的数字减去 2 四次:
0,100000001490116119384765625
我使用了 MS 计算器
**
现在是第二部分。从十进制转换为二进制。
**
我拿数字 0.1
它很容易,因为没有整数部分。第一个符号位 - 它是 0。
指数和有效精度我现在将计算。逻辑乘以 2 整数 (0.1*2=0.2),如果它大于 1,则减去并继续。
这个数字是 .00011001100110011001100110011,标准说我们必须向左移动才能得到 1。(某事)。你怎么看,我们需要 4 个班次,从这个数字计算指数 (127-4=123)。现在的 Significand 精度是
10011001100110011001100(并且有丢失的位)。
现在是整数。符号位 0 指数是 123 (01111011),有效精度是 10011001100110011001100 和整体,它是
00111101110011001100110011001100让我们将其与上一章
00111101110011001100110011001101 中的那些进行比较
如您所见,最后一位不相等。这是因为我截断了这个数字。CPU 和编译器知道 是 Significand 精度之后无法保持的东西,只需将最后一位设置为 1。
评论
网络上的很多答案都错误地查看浮点数之间的差异,这只适用于特殊情况,可靠的方法是查看相对差异,如下所示:
// Floating point comparison:
bool CheckFP32Equal(float referenceValue, float value)
{
const float fp32_epsilon = float(1E-7);
float abs_diff = std::abs(referenceValue - value);
// Both identical zero is a special case
if( referenceValue==0.0f && value == 0.0f)
return true;
float rel_diff = abs_diff / std::max(std::abs(referenceValue) , std::abs(value) );
if(rel_diff < fp32_epsilon)
return true;
else
return false;
}
另一个几乎完全相同的问题与这个问题有关,因此答案晚了几年。我认为上述答案并不完整。
int fun1 ( void )
{
float x=0.7;
if(x==0.7) return(1);
else return(0);
}
int fun2 ( void )
{
float x=1.1;
if(x==1.1) return(1);
else return(0);
}
int fun3 ( void )
{
float x=1.0;
if(x==1.0) return(1);
else return(0);
}
int fun4 ( void )
{
float x=0.0;
if(x==0.0) return(1);
else return(0);
}
int fun5 ( void )
{
float x=0.7;
if(x==0.7f) return(1);
else return(0);
}
float fun10 ( void )
{
return(0.7);
}
double fun11 ( void )
{
return(0.7);
}
float fun12 ( void )
{
return(1.0);
}
double fun13 ( void )
{
return(1.0);
}
Disassembly of section .text:
00000000 <fun1>:
0: e3a00000 mov r0, #0
4: e12fff1e bx lr
00000008 <fun2>:
8: e3a00000 mov r0, #0
c: e12fff1e bx lr
00000010 <fun3>:
10: e3a00001 mov r0, #1
14: e12fff1e bx lr
00000018 <fun4>:
18: e3a00001 mov r0, #1
1c: e12fff1e bx lr
00000020 <fun5>:
20: e3a00001 mov r0, #1
24: e12fff1e bx lr
00000028 <fun10>:
28: e59f0000 ldr r0, [pc] ; 30 <fun10+0x8>
2c: e12fff1e bx lr
30: 3f333333 svccc 0x00333333
00000034 <fun11>:
34: e28f1004 add r1, pc, #4
38: e8910003 ldm r1, {r0, r1}
3c: e12fff1e bx lr
40: 66666666 strbtvs r6, [r6], -r6, ror #12
44: 3fe66666 svccc 0x00e66666
00000048 <fun12>:
48: e3a005fe mov r0, #1065353216 ; 0x3f800000
4c: e12fff1e bx lr
00000050 <fun13>:
50: e3a00000 mov r0, #0
54: e59f1000 ldr r1, [pc] ; 5c <fun13+0xc>
58: e12fff1e bx lr
5c: 3ff00000 svccc 0x00f00000 ; IMB
为什么 fun3 和 fun4 返回一个而不是其他的?为什么 fun5 有效?
这是关于语言的。该语言说 0.7 是双精度,除非您使用此语法 0.7f,否则它是单精度。所以
float x=0.7;
double 0.7 转换为 single 并存储在 x 中。
if(x==0.7) return(1);
该语言说我们必须提升到更高的精度,因此 x 中的 single 被转换为 double,并与 double 进行比较 0.7。
00000028 <fun10>:
28: e59f0000 ldr r0, [pc] ; 30 <fun10+0x8>
2c: e12fff1e bx lr
30: 3f333333 svccc 0x00333333
00000034 <fun11>:
34: e28f1004 add r1, pc, #4
38: e8910003 ldm r1, {r0, r1}
3c: e12fff1e bx lr
40: 66666666 strbtvs r6, [r6], -r6, ror #12
44: 3fe66666 svccc 0x00e66666
单人 3F333333 双 3fe66666666666666
正如Alexandr所指出的,如果这个答案仍然是IEEE 754,那么一个是
seeeeeeeffffffffffffff
而双倍是
seeeeeeeeeeeffff
具有 52 位分数,而不是单通道的 23 位。
00111111001100110011... single
001111111110011001100110... double
0 01111110 01100110011... single
0 01111111110 01100110011... double
就像以 10 为基数的 1/3 是 0.3333333......永远。我们在这里有一个重复的模式 0110
01100110011001100110011 single, 23 bits
01100110011001100110011001100110.... double 52 bits.
这就是答案。
if(x==0.7) return(1);
x 包含 01100110011001100110011 作为其分数,当它被转换回来时 将分数加倍是
01100110011001100110011000000000....
这不等于
01100110011001100110011001100110...
但在这里
if(x==0.7f) return(1);
这种提升不会发生,相同的位模式会相互比较。
为什么 1.0 有效?
00000048 <fun12>:
48: e3a005fe mov r0, #1065353216 ; 0x3f800000
4c: e12fff1e bx lr
00000050 <fun13>:
50: e3a00000 mov r0, #0
54: e59f1000 ldr r1, [pc] ; 5c <fun13+0xc>
58: e12fff1e bx lr
5c: 3ff00000 svccc 0x00f00000 ; IMB
0011111110000000...
0011111111110000000...
0 01111111 0000000...
0 01111111111 0000000...
在这两种情况下,分数都是零。因此,从双倍到单倍再到双倍的转换不会损失精度。它准确地从单重命名为双精度,并且两个值的位比较有效。
halfdan投票和检查的最高答案是正确答案,这是一个混合精度的情况,你永远不应该做相等的比较。
为什么没有在那个答案中显示。0.7 失败 1.0 有效。为什么没有显示 0.7 失败。重复的问题 1.1 也会失败。
编辑
平等可以在这里从问题中取出,这是一个已经回答过的不同问题,但它是同一个问题,并且还有“什么......”最初的震惊。
int fun1 ( void )
{
float x=0.7;
if(x<0.7) return(1);
else return(0);
}
int fun2 ( void )
{
float x=0.6;
if(x<0.6) return(1);
else return(0);
}
Disassembly of section .text:
00000000 <fun1>:
0: e3a00001 mov r0, #1
4: e12fff1e bx lr
00000008 <fun2>:
8: e3a00000 mov r0, #0
c: e12fff1e bx lr
为什么一个显示小于,另一个显示不小于?当它们应该相等时。
从上面我们知道 0.7 的故事。
01100110011001100110011 single, 23 bits
01100110011001100110011001100110.... double 52 bits.
01100110011001100110011000000000....
小于。
01100110011001100110011001100110...
0.6 是不同的重复模式 0011 而不是 0110。
但是当从双倍转换为单倍时或一般情况下表示 作为单个 IEEE 754。
00110011001100110011001100110011.... double 52 bits.
00110011001100110011001 is NOT the fraction for single
00110011001100110011010 IS the fraction for single
IEEE 754 使用舍入模式、向上舍入、向下舍入或舍入到零。默认情况下,编译器倾向于向上舍入。如果你还记得小学时四舍五入,12345678如果我想四舍五入到从顶部开始的第 3 位数字,那就是 12300000,但如果后面的数字是 1235000 或更大,则四舍五入。5 是 10 的 1/2,二进制 1 中的基数(十进制)是基数的 1/2,所以如果我们要四舍五入的位置之后的数字是 1,那么四舍五入,否则不要。因此,对于 0.7,我们没有四舍五入,对于 0.6,我们四舍五入。
现在很容易看出
00110011001100110011010
由于 (x<0.7) 转换为双精度
00110011001100110011010000000000....
大于
00110011001100110011001100110011....
因此,不必谈论使用等于,问题仍然存在,0.7 是双精度,0.7f 是单精度,如果它们不同,则操作将提升到最高精度。
保存在变量和常量中的指向值的数据类型不同。这是数据类型精度的差异。 如果将 f 变量的数据类型更改为 double,它将打印 equal,这是因为浮点数中的常量默认存储在 double 中,非浮点存储在 long 中,double 的精度高于 float。如果您看到浮点数转换为二进制转换的方法,那就完全清楚了
评论
0.7
不是“浮点文字”,而是一个 .double