2010-07-28 25 views
19

Duplicar posible:
problem in comparing double values in C#¿Por qué este ciclo nunca termina?

He leído en otro lugar, pero en realidad se olvide la respuesta por lo que pido aquí de nuevo. Este bucle nunca se parece terminar sin codificar en cualquier idioma (lo prueba en C#, C++, Java ...):

double d = 2.0; 
while(d != 0.0){ 
    d = d - 0.2; 
} 
+2

Nunca use '==' con valores flotantes. Tal vez usar algo como 'f> epsilon'. – pascal

+20

[Habían pasado unos días, supongo que estábamos por vencer.] (Http://docs.sun.com/source/806-3568/ncg_goldberg.html) – GManNickG

+0

En Java, creo que existe el modificador "Strictfp" para tales situaciones –

Respuesta

32

cálculos de punto flotante no son perfectamente preciso. Obtendrá un error de representación porque 0.2 no tiene una representación exacta como un número de punto flotante binario, por lo que el valor no se convierte en exactamente igual a cero. Trate de añadir una declaración de depuración para ver el problema:

double d = 2.0; 
while (d != 0.0) 
{ 
    Console.WriteLine(d); 
    d = d - 0.2; 
} 
 
2 
1,8 
1,6 
1,4 
1,2 
1 
0,8 
0,6 
0,4 
0,2 
2,77555756156289E-16 // Not exactly zero!! 
-0,2 
-0,4 

Una forma de resolverlo es utilizar el tipo decimal.

+1

¿Y por qué hay errores redondos? Escriba 'doble' almacena dígitos binarios. Como 0,2 binario escrito es una fracción periódica, debemos cortar algo para escribir el número en N bits. – adf88

27

(Para una cosa que no esté utilizando la misma variable a lo largo, pero voy a asumir que es un error tipográfico :)

0.2 no es realmente 0,2. Es el valor double más cercano a 0.2. Cuando hayas restado 10 veces de 2.0, no terminarás con exactamente 0.0.

En C# puede cambiar para utilizar el tipo decimal lugar, que trabajará:

// Works 
decimal d = 2.0m; 
while (d != 0.0m) { 
    d = d - 0.2m; 
} 

Esto funciona porque el decimal tipo hace representan los valores decimales como 0.2 con precisión (dentro de ciertos límites, es un 128- tipo de bit). Cada valor involucrado es precisamente representable, por lo que funciona. Lo que no sería trabajo sería la siguiente:

decimal d = 2.0m; 
while (d != 0.0m) { 
    d = d - 1m/3m; 
} 

Aquí, "un tercio" no es exactamente representable por lo que nos encontramos con el mismo problema que antes.

En general, no es una buena idea realizar comparaciones de igualdad exactas entre los números de punto flotante; generalmente los compara dentro de una cierta tolerancia.

Tengo artículos en floating binary point y floating decimal point de un contexto C#/.NET, que explican las cosas con más detalle.

1

f está sin inicializar;)

Si se refiere a:

double f = 2.0; 

Esto puede ser un efecto de arthimetic no precisa en variables dobles.

3

Usted es mejor usar

while(f > 0.0) 

* Editar: Véase el comentario de Pascal a continuación. Pero si necesita ejecutar un ciclo una cantidad integral, determinista de veces, más bien use un tipo de datos integral.

+0

Sé que debería usarlo, pero ¿por qué f! = 0.0 no funciona? –

+1

Puede ejecutar demasiadas veces, si la última 'f' es 2.0e-16 ... – pascal

1

es por la precisión del punto flotante. use while (d> 0.0), o si debe,

while (Math.abs(d-0.0) > some_small_value){ 

} 
2

El problema es la aritmética de coma flotante. Si no hay una representación binaria exacta para un número, entonces solo puede almacenar el número más cercano (al igual que no pudo almacenar el número 1/3 en decimal; solo puede almacenar algo como 0.33333333 por una longitud de '3's). Esto significa que la aritmética en números de punto flotante a menudo no es totalmente precisa. Pruebe algo como lo siguiente (Java):

public class Looping { 

    public static void main(String[] args) { 

     double d = 2.0; 
     while(d != 0.0 && d >= 0.0) { 
      System.out.println(d); 
      d = d - 0.2; 
     } 

    } 

} 

El resultado debe ser algo como:

2.0 
1.8 
1.6 
1.4000000000000001 
1.2000000000000002 
1.0000000000000002 
0.8000000000000003 
0.6000000000000003 
0.4000000000000003 
0.2000000000000003 
2.7755575615628914E-16 

Y ahora usted debería ser capaz de ver por qué la condición d == 0 nunca sucede. . (El último número no es un número que es muy cercano a 0 pero no del todo

Para otro ejemplo de punto de rareza flotante, intente esto:

public class Squaring{ 

    public static void main(String[] args) { 

     double d = 0.1; 
     System.out.println(d*d); 

    } 

} 

Debido a que no existe una representación binaria de exactamente 0.1, la cuadratura que no produce el resultado que se puede esperar (0.01), pero en realidad algo así como 0.010000000000000002!

+1

" Nunca totalmente exacto "está exagerándola, IMO. El hecho de que no * cada * valor decimal no sea exactamente representable en el punto flotante binario no hace la suma de 0.25 y 0.25 exactamente representados para obtener exactamente 0.5 menos exacto, por ejemplo. –

+0

Editado, gracias Jon, fue un poco exagerado. – Stephen

10

Recuerdo haber comprado un Sinclair ZX-81, trabajando mi camino a través de la excelente manual de programación básico, y casi volver a la tienda cuando me encontré con mi primer error de redondeo de coma flotante.

Nunca me hubiera imaginado que las personas seguirían teniendo estos problemas 27.99998 años después.

+2

El manual de ZX-Spectrum viene con una explicación detallada de este problema. Recuerdo haber pensado mucho sobre eso (alrededor de los 10 u 11 años) y decir "oh, eso tiene sentido". Era un manual muy bueno ... –

+7

+1 por 27.99998 años :-) –

0

No se detiene debido a 0.2 no yo representaba precisamente en complemento de dos, así que el bucle nunca se ejecuta la prueba 0.0==0.0

+0

El complemento a dos no tiene ninguna conexión con los errores de redondeo de los números de coma flotante. – Egon

0

Como otros han dicho, este es sólo un problema fundamental que se obtiene cuando se hace de punto flotante aritmética a cualquier base. Simplemente sucede que base-2 es el más común en las computadoras (porque admite una implementación eficiente del hardware).

La mejor solución, si es posible, es cambiar a usar algún tipo de representación de cociente del número para su bucle, haciendo que el valor de coma flotante se derive de eso. OK, eso suena exagerado! Para su caso específico, me gustaría escribir como:

int dTimes10 = 20; 
double d; 
while(dTimes10 != 0) { 
    dTimes10 -= 2; 
    d = dTimes10/10.0; 
} 

Aquí, realmente estamos trabajando con fracciones [20/10, 18/10, 16/10, ..., 2/10, 0/10] donde la iteración se hace con números enteros (es decir, fáciles de corregir) en el numerador con un denominador fijo, antes de convertir a coma flotante. Si puede reescribir sus iteraciones reales para que funcionen de esta manera, tendrá un gran éxito (y en realidad no son mucho más costosas que lo que estaba haciendo antes, lo cual es una gran compensación para obtener la corrección).

Si no puede hacer esto, necesita usar equal-in-epsilon como su comparación. Aproximadamente, eso reemplaza a d != target con abs(d - target) < ε, donde la selección de ε (epsilon) a veces puede ser incómoda. Básicamente, el valor correcto de ε depende de un grupo de factores, pero es mejor que se seleccione como 0.001 para la iteración de ejemplo dada la escala del valor del paso (es decir, es medio por ciento de la magnitud del paso, por lo que cualquier elemento dentro de eso va a ser un error en lugar de informativo).

Cuestiones relacionadas