2011-02-01 12 views
10

I estaba ejecutando un algoritmo para calcular logaritmos naturales en C.doble es igual a 0 problema en C

double taylor_ln(int z) { 
    double sum = 0.0; 
    double tmp = 1.0; 

    int i = 1; 
    while(tmp != 0.0) { 
     tmp = (1.0/i) * (pow(((z - 1.0)/(z + 1.0)), i)); 
     printf("(1.0/%d) * (pow(((%d - 1.0)/(%d + 1.0)), %d)) = %f\n", i, z, z, i, tmp); 
     sum += tmp; 
     i += 2; 
    } 

    return sum * 2; 
} 

Como se muestra por la instrucción de impresión, tmp es igual a 0,0 con el tiempo, sin embargo, el bucle continúa. ¿Qué podría estar causando esto?

estoy en Fedora 14 AMD64 y compilar con:

clang -lm -o taylor_ln taylor_ln.c 

Ejemplo:

$ ./taylor_ln 2 
(1.0/1) * (pow(((2 - 1.0)/(2 + 1.0)), 1)) = 0.333333 
(1.0/3) * (pow(((2 - 1.0)/(2 + 1.0)), 3)) = 0.
(1.0/5) * (pow(((2 - 1.0)/(2 + 1.0)), 5)) = 0.000823 
(1.0/7) * (pow(((2 - 1.0)/(2 + 1.0)), 7)) = 0.000065 
(1.0/9) * (pow(((2 - 1.0)/(2 + 1.0)), 9)) = 0.000006 
(1.0/11) * (pow(((2 - 1.0)/(2 + 1.0)), 11)) = 0.000001 
(1.0/13) * (pow(((2 - 1.0)/(2 + 1.0)), 13)) = 0.000000 
(1.0/15) * (pow(((2 - 1.0)/(2 + 1.0)), 15)) = 0.000000 
(1.0/17) * (pow(((2 - 1.0)/(2 + 1.0)), 17)) = 0.000000 
(1.0/19) * (pow(((2 - 1.0)/(2 + 1.0)), 19)) = 0.000000 
(1.0/21) * (pow(((2 - 1.0)/(2 + 1.0)), 21)) = 0.000000 
and so on... 
+0

Guau, cuatro personas con la misma respuesta al mismo tiempo. – mgiuca

+0

Eche un vistazo a: http://stackoverflow.com/questions/4664662/understanding-floating-point-problems/4664784. Los números de coma flotante pueden ser muy difíciles si no los conoces bien. –

+0

@mgiuca: entonces _debe_ estar en lo cierto :-) – paxdiablo

Respuesta

9

La comparación de punto flotante es exacta, por lo 10^-10 no es lo mismo que 0.0.

Básicamente, usted debe hacer la comparación alguna diferencia tolerable, por ejemplo 10^-7 basado en el número de decimales que escribe el cabo, que puede llevarse a cabo como:

while(fabs(tmp) > 10e-7) 
+5

Y aquí está el enlace obligatorio: [Lo que cada informático debe saber sobre la aritmética de punto flotante] (http://citeseer.ist.psu.edu/viewdoc/download;jsessionid=86013D0FEFFA6CD1A626176C5D4EF9E2?doi=10.1.1.102.244&rep= rep1 y type = pdf) – chrisaycock

+0

Dos cosas: abs es una operación entera; usa 'fabs'. Y desea que * mayor que * algún umbral, no menos de. – mgiuca

+0

@chrisaycock: Deberías haber hecho una respuesta ... es (o la versión simplificada) es la respuesta correcta para la mayoría de las preguntas relacionadas con coma flotante aquí. –

1

No utilice operaciones exactas de igualdad cuando se trata de números de punto flotante. Aunque su número puede ser , busque como 0, probablemente sea algo así como 0.00000000000000000000001.

Verá esto si usa %.50f en lugar de %f en las cadenas de formato. Este último usa un valor predeterminado razonable para las posiciones decimales (6 en su caso) pero el primero declara explícitamente que desea mucho.

Por razones de seguridad, utilice un delta para comprobar si está lo suficientemente cerca, como por ejemplo:

if (fabs (val) < 0.0001) { 
    // close enough. 
} 

Obviamente, el delta depende por completo de sus necesidades. Si está hablando de dinero, 10 -5 puede ser suficiente. Si eres físico, probablemente deberías elegir un valor menor.

Por supuesto, si usted es un matemático, existe inexactitud es lo suficientemente pequeño :-)

0

El hecho de que un número aparecerá como "0,000000 "no significa que es igual a 0.0. La visualización decimal de los números tiene menos precisión que la que puede almacenar un doble.

Es posible que su algoritmo esté llegando a un punto muy cercano a 0, pero el siguiente paso se mueve tan poco que redondea a la misma cosa que antes, y por lo tanto nunca se acerca a 0 (simplemente entra en un ciclo infinito).

En general, no debe comparar los números de coma flotante con == y !=. Siempre debe verificar si se encuentran dentro de un cierto rango pequeño (generalmente llamado épsilon). Por ejemplo:

while(fabs(tmp) >= 0.0001) 

Luego se detendrá cuando se pone razonablemente cerca de la declaración 0.

0

La impresión está mostrando un valor redondeado, que no está imprimiendo la máxima precisión posible. Entonces tu ciclo aún no ha llegado a cero.

(Y, como han mencionado otros, debido a problemas de redondeo que en realidad podría llegar nunca. Al comparar el valor con un pequeño límite es por lo tanto más robusto que la comparación de igualdad con el 0.0.)

0

Un montón de discusión de la causa, pero aquí es una solución alternativa:

double taylor_ln(int z) 
{ 
    double sum = 0.0; 
    double tmp, old_sum; 
    int i = 1; 
    do 
    { 
     old_sum = sum; 
     tmp = (1.0/i) * (pow(((z - 1.0)/(z + 1.0)), i)); 
     printf("(1.0/%d) * (pow(((%d - 1.0)/(%d + 1.0)), %d)) = %f\n", 
       i, z, z, i, tmp); 
     sum += tmp; 
     i += 2; 
    } while (sum != old_sum); 
    return sum * 2; 
} 

Este enfoque se centra en si cada valor decreciente de tmp hace una diferencia tangible para resumir. Es más fácil que calcular un umbral desde 0 en el que tmp se vuelve insignificante, y probablemente termina antes sin cambiar el resultado.

Tenga en cuenta que cuando suma un número relativamente grande con uno relativamente pequeño, los dígitos significativos en el resultado limitan la precisión. Por el contrario, si sumas varias pequeñas y luego la agregas a la grande, puedes tener suficiente para subir un poco la grande. En su algoritmo, los valores de tmp pequeños no se sumaron entre sí de todos modos, por lo que no hay acumulación a menos que cada uno afecte a la suma, por lo tanto, el enfoque anterior funciona sin comprometer aún más la precisión.