salida extraña en comparación de flotante con literal flotante
float f = 0.7;
if( f == 0.7 )
printf("equal");
else
printf("not equal");
¿Por qué es la salida not equal
?
¿Por qué pasó esto?
Esto sucede porque en su declaración
if(f == 0.7)
el 0,7 se trata como un doble. Pruebe 0.7f para asegurarse de que el valor se trate como flotante:
if(f == 0.7f)
Pero como Michael sugirió en los comentarios a continuación, nunca debes probar la igualdad exacta de los valores de punto flotante.
Esta respuesta complementa las existentes: tenga en cuenta que 0,7 no se puede representar exactamente ni como flotante (ni como doble). Si estuviera representado exactamente, entonces no habría pérdida de información al convertir a flotante y luego volver a duplicar, y no tendría este problema.
Incluso se podría argumentar que debería haber una advertencia del compilador para las constantes literales de punto flotante que no se pueden representar exactamente, especialmente cuando el estándar es tan confuso en cuanto a si el redondeo se realizará en tiempo de ejecución en el modo que se ha establecido como en ese momento o en tiempo de compilación en otro modo de redondeo.
Todos los números no enteros que se pueden representar exactamente tienen 5
como último dígito decimal. Desafortunadamente, lo contrario no es cierto: algunos números tienen 5
como último dígito decimal y no se pueden representar exactamente. Todos los números enteros pequeños se pueden representar exactamente, y la división por una potencia de 2 transforma un número que se puede representar en otro que se puede representar, siempre y cuando no entre en el ámbito de los números desnormalizados.
En primer lugar, miremos el interior del número flotante. Tomo 0.1f y tiene 4 bytes de longitud (binary32), en hexadecimal es
3D CC CC CD .
Según el estándar IEEE 754 para convertirlo a decimal debemos hacer así:
En binario 3D CC CC CD es
0 01111011 1001100 11001100 11001101
aquí el primer dígito es un bit de signo. 0 significa (-1)^0 que nuestro número es positivo.
Los segundos 8 bits son un exponente. En binario es 01111011, en decimal 123. Pero el exponente real es 123-127 (siempre 127) = -4 , lo que significa que debemos multiplicar el número que obtendremos por 2^ (-4).
Los últimos 23 bytes son la precisión significativa. Allí, el primer bit lo multiplicamos por 1/ (2^1) (0,5), el segundo por 1/ (2^2) (0,25) y así sucesivamente. Esto es lo que obtenemos:
Necesitamos sumar todos los números (potencia de 2) y sumarle 1 (siempre 1, por defecto). Es
1,60000002384185791015625
Ahora multipliquemos este número por 2^ (-4), es del exponente. Simplemente dividimos el número anterior entre 2 cuatro veces:
0,100000001490116119384765625
Utilicé MS Calculator
**
Ahora la segunda parte. Conversión de decimal a binario.
**
Tomo el número 0.1.
Es más fácil porque no hay una parte entera. Primer bit de signo: es 0. La precisión del exponente y el significado la calcularé ahora. La lógica es multiplicar por 2 números enteros (0.1*2=0.2) y si es mayor que 1 restar y continuar.
Y el número es .00011001100110011001100110011, el estándar dice que debemos desplazarnos a la izquierda antes de obtener 1. (algo). Como ves, necesitamos 4 turnos, a partir de este número calculamos el Exponente (127-4= 123 ). Y la precisión significativa ahora es 10011001100110011001100 (y se pierden bits).
Ahora el número entero. Bit de signo 0 El exponente es 123 ( 01111011 ) y la precisión significativa es 10011001100110011001100 y el total es 00111101110011001100110011001100
comparémoslo con los que tenemos del capítulo anterior 001111011100110011001 10011001101
Como puede ver, los últimos bits no son iguales. Es porque trunco el número. La CPU y el compilador saben que hay algo después de que la precisión significativa no se puede mantener y simplemente establecen el último bit en 1.
Otra pregunta casi exacta estaba vinculada a esta, de ahí la respuesta tardía. No creo que las respuestas anteriores estén completas.
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
¿Por qué fun3 y fun4 devolvieron uno y no los demás? ¿Por qué funciona fun5?
Se trata del idioma. El lenguaje dice que 0.7 es doble, a menos que uses esta sintaxis 0.7f, entonces es simple. Entonces
float x=0.7;
el doble 0,7 se convierte en un simple y se almacena en x.
if(x==0.7) return(1);
El lenguaje dice que tenemos que promover a una mayor precisión para que el simple en x se convierta en un doble y se compare con el doble 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
sencillo 3f333333 doble 3fe6666666666666
Como Alexandr señaló, si esa respuesta sigue siendo IEEE 754, una sola es
a vereeeeeefffffffffffffffffffff
y el doble es
A veraaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
con 52 bits de fracción en lugar de los 23 que tiene ese single.
00111111001100110011... single
001111111110011001100110... double
0 01111110 01100110011... single
0 01111111110 01100110011... double
Al igual que 1/3 en base 10 es 0,3333333... para siempre. Tenemos un patrón repetido aquí 0110
01100110011001100110011 single, 23 bits
01100110011001100110011001100110.... double 52 bits.
Y aquí está la respuesta.
if(x==0.7) return(1);
x contiene 01100110011001100110011 como fracción, cuando eso se convierte nuevamente al doble de la fracción es
01100110011001100110011000000000....
que no es igual a
01100110011001100110011001100110...
pero aquí
if(x==0.7f) return(1);
Esa promoción no se produce al comparar los mismos patrones de bits entre sí.
¿Por qué funciona 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...
En ambos casos la fracción es todo ceros. Por lo tanto, al convertir de doble a simple y a doble no hay pérdida de precisión. Convierte de simple a doble exactamente y la comparación de bits de los dos valores funciona.
La respuesta más votada y verificada por halfdan es la respuesta correcta, este es un caso de precisión mixta Y nunca debes hacer una comparación igual.
El por qué no se mostró en esa respuesta. 0.7 falla 1.0 funciona. No se muestra por qué falló 0.7. Una pregunta duplicada 1.1 también falla.
Editar
Los iguales se pueden sacar del problema aquí, es una pregunta diferente que ya está respondida, pero es el mismo problema y además tiene el shock inicial de «qué carajo…».
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
¿Por qué uno aparece como menor que y el otro no menor que? Cuando deberían ser iguales.
Desde arriba conocemos la historia del 0,7.
01100110011001100110011 single, 23 bits
01100110011001100110011001100110.... double 52 bits.
01100110011001100110011000000000....
es menos que.
01100110011001100110011001100110...
0.6 es un patrón repetido diferente 0011 en lugar de 0110.
pero cuando se convierte de doble a simple o en general cuando se representa como simple IEEE 754.
00110011001100110011001100110011.... double 52 bits.
00110011001100110011001 is NOT the fraction for single
00110011001100110011010 IS the fraction for single
IEEE 754 utiliza modos de redondeo, redondeo hacia arriba, redondeo hacia abajo o redondeo a cero. Los compiladores tienden a redondear por defecto. Si recuerdas redondear en la escuela primaria 12345678, si quisiera redondear al tercer dígito desde arriba sería 12300000, pero redondea al siguiente dígito 1235000 si el dígito siguiente es 5 o más, entonces redondea hacia arriba. 5 es la mitad de 10, la base (decimal) en binario 1 es la mitad de la base, por lo que si el dígito después de la posición que queremos redondear es 1, entonces redondee hacia arriba, de lo contrario no lo haga. Entonces, para 0,7 no redondeamos hacia arriba, para 0,6 sí redondeamos hacia arriba.
Y ahora es fácil ver que
00110011001100110011010
convertido a doble debido a (x<0.7)
00110011001100110011010000000000....
es mayor que
00110011001100110011001100110011....
Entonces, sin tener que hablar sobre el uso de iguales, el problema aún se presenta: 0.7 es doble 0.7f es simple, la operación se promueve a la mayor precisión si difieren.