strange output in comparison of float with float literal
1 2 3 4 5 | float f = 0.7; if( f == 0.7 ) printf("equal"); else printf("not equal"); |
为什么输出是
为什么会这样?
这是因为在你的陈述中
1 | if(f == 0.7) |
0.7被视为双精度。尝试0.7f以确保该值被视为浮点值:
1 | if(f == 0.7f) |
但是正如Michael在下面的注释中建议的那样,您永远不应该测试浮点值是否完全相等。
这个答案是对现有答案的补充:请注意0.7既不能精确地表示为浮点数(也不能表示为双精度数)。如果它是精确表示的,那么在转换为float然后再转换为double时就不会丢失信息,您也不会遇到这个问题。
甚至可以说,对于无法精确表示的文字浮点常量,应该有一个编译器警告,特别是当标准如此模糊时,关于舍入将在运行时在设置为该时间的模式下进行还是在编译时在另一个舍入模式下进行。
所有可以精确表示的非整数都将
首先让我们看一下浮点数。我取0.1f,它是4字节长(二进制32),用十六进制表示3D CC CC光盘。根据标准IEEE 754,要将其转换为十进制,我们必须这样做:
在二进制3D CC CC CD中0 0111011 1001100 11001100 11001101号这里第一个数字是符号位。0表示(-1)^0,表示我们的数字是正数。第二个8位是指数。在二进制中,它是0111011-在十进制123中。但真正的指数是123-127(总是127)=-4,这意味着我们需要将得到的数字乘以2^(-4)。最后23个字节是有效位精度。在这里,第一位乘以1/(2^1)(0.5),第二位乘以1/(2^2)(0.25),依此类推。我们得到的是:
我们需要加上所有的数字(2的幂),再加上1(总是1,按标准)。它是1600万2384185791015625现在让我们把这个数字乘以2^(-4),它是指数。我们只是把上面的数字除以2,4次:01000000011490116119384765625我用的是MS计算器**
现在是第二部分。从十进制转换为二进制。**我取0.1它很容易,因为没有整数部分。第一个符号位-它是0。指数和有效位精度,我现在计算。逻辑乘以2整数(0.1*2=0.2),如果大于1,则继续。号码是.000110011001100110011001100110011,斯坦达特说我们必须在得到1之前向左移动。你怎么看,我们需要4个移位,从这个数字计算指数(127-4=123)。有效位精度现在是100110011001100110011001100(并且有丢失的位)。现在是整数。符号位0指数为123(0111011),有效位精度为10011001100110011001100,整数为0011110110011001100110011001100我们把它和上一章的比较一下邮编:001111011001100110011001101如你所见,最后一点是不平等的。因为我截断了这个数字。CPU和编译器知道,在有效位精度之后是不能保持的,只需将最后一位设置为1。
另一个近乎精确的问题与这一问题联系在一起,因此答案迟了几年。我认为上述答案不完整。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | 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,否则它是一个单精度数。所以
1 | float x=0.7; |
double 0.7转换为single并存储在x中。
1 | if(x==0.7) return(1); |
该语言表示,我们必须提高精度,这样x中的单精度转换为双精度,并与双精度0.7进行比较。
1 2 3 4 5 6 7 8 9 10 11 | 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 |
单3F33 33双3FE6666666666
正如亚历山德拉指出的那样,如果这个答案仍然是IEEE754,那么一个是
seeeeeeeefffffffffffffffffffffff
双是
seeeeeeeeeeeffffffffffffffffffffffffffffffffffffffffffffffffffff
分数是52位而不是23位。
1 2 3 4 5 | 00111111001100110011... single 001111111110011001100110... double 0 01111110 01100110011... single 0 01111111110 01100110011... double |
就像10的1/3是0.3333333…永远。我们这里有一个重复模式0110
1 2 | 01100110011001100110011 single, 23 bits 01100110011001100110011001100110.... double 52 bits. |
这就是答案。
1 | if(x==0.7) return(1); |
X包含01100110011001100110011作为其分数,当转换回分数加倍是
1 | 01100110011001100110011000000000.... |
不等于
1 | 01100110011001100110011001100110... |
但是这里
1 | if(x==0.7f) return(1); |
这种提升不会发生,相同的位模式会相互比较。
1.0为什么有效?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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... |
在这两种情况下,分数都是零。因此,从双精度转换为单精度转换为双精度没有损失。它精确地从单值转换为双值,两个值的位比较工作。
哈夫丹的最高投票和检查答案是正确的答案,这是一个混合精度的情况,你不应该做一个平等的比较。
答案中没有说明原因。0.7未通过1.0工程。为什么没有显示0.7失败。一个重复的问题1.1也失败了。
编辑
等号可以从这个问题中去掉,这是一个已经被回答过的不同的问题,但它是相同的问题,并且有"什么……"的初始冲击。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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的故事。
1 2 3 4 | 01100110011001100110011 single, 23 bits 01100110011001100110011001100110.... double 52 bits. 01100110011001100110011000000000.... |
小于。
1 | 01100110011001100110011001100110... |
0.6是不同的重复模式0011,而不是0110。
但是,当从双精度转换为单精度时,或者通常在表示时作为一个单独的IEEE 754。
1 2 3 | 00110011001100110011001100110011.... double 52 bits. 00110011001100110011001 is NOT the fraction for single 00110011001100110011010 IS the fraction for single |
IEEE754使用取整模式,向上取整、向下取整或向零取整。默认情况下,编译器倾向于进行汇总。如果你记得在12345678小学四舍五入,如果我想从顶部四舍五入到第三个数字,它将是12300000,但如果后面的数字是5或更大,则四舍五入到下一个数字1235000。5是10的1/2,二进制1中的基数(十进制)是基数的1/2,所以如果我们要四舍五入的位置后的数字是1,那么四舍五入,否则不。所以对于0.7,我们不求和,对于0.6,我们求和。
现在很容易看出
1 | 00110011001100110011010 |
由于(x<0.7)转换为双精度
1 | 00110011001100110011010000000000.... |
大于
1 | 00110011001100110011001100110011.... |
所以不用说用等号的问题仍然存在,0.7是双0.7f是单的,如果操作不一致,就提升到最高精度。
网上的很多答案都犯了错误,就是看浮点数之间的异常差,这只对特殊情况有效,稳健的方法是看下面的相对差:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | // 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; } |
正如其他评论者所指出的,您面临的问题是,测试浮点数之间的完全等效性通常是不安全的,因为初始化错误或计算中的舍入错误可能会带来细微的差异,从而导致==运算符返回错误。
更好的做法是
1 2 3 4 5 | float f = 0.7; if( fabs(f - 0.7) < FLT_EPSILON ) printf("equal"); else printf("not equal"); |
假设flt_epsilon被定义为平台的适当小的浮点值。
由于舍入或初始化错误不太可能超过flt_epsilon的值,这将为您提供所需的可靠等效性测试。
如果将f的数据类型改为double,则打印为equal,这是因为双精度浮点和长时间非浮点存储的常量,双精度高,浮点精度低,双精度值存储在64位二进制中,浮点值存储在32位二进制中,如果您看到浮点数的方法,就会完全清楚。转换为二进制转换。
考虑一下:
1 2 3 4 5 6 7 8 9 10 11 | int main() { float a = 0.7; if(0.7 > a) printf("Hi "); else printf("Hello "); return 0; } |
如果(0.7>a),这里a是一个浮点变量,
例子:
1 2 3 4 5 6 7 | int main() { float a=0.7; printf("%.10f %.10f ",0.7, a); return 0; } |
输出:0.70亿0.6999999881