salida extraña en comparación de flotante con literal flotante

Resuelto Ashish asked hace 14 años • 8 respuestas
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?

Ashish avatar Dec 03 '09 18:12 Ashish
Aceptado

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.

halfdan avatar Dec 03 '2009 11:12 halfdan

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 5como último dígito decimal. Desafortunadamente, lo contrario no es cierto: algunos números tienen 5como ú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.

Pascal Cuoq avatar Dec 03 '2009 11:12 Pascal Cuoq

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í:

ingrese la descripción de la imagen aquí
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:


ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí

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.
ingrese la descripción de la imagen aquí





Alexandr avatar Jul 12 '2016 09:07 Alexandr

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.

old_timer avatar Dec 30 '2018 08:12 old_timer