2009-08-17 10 views
5

Tengo un programa simple en C++ compilado usando gcc 4.2.4 en Ubuntu 8.04 de 32 bits. Tiene un bucle for en el que una variable double se incrementa de cero a uno con un determinado tamaño de paso. Cuando el tamaño del paso es 0.1, el comportamiento es el esperado. Pero cuando el tamaño del paso es '0.05', el ciclo sale después de 0.95. ¿Puede alguien decirme por qué está pasando esto? La salida sigue el código fuente a continuación.For-loop en C++ utilizando doble ruptura un paso antes, no se alcanzó el valor límite

#include <iostream> 

using namespace std; 

int main() 
{ 
    double rangeMin = 0.0; 
    double rangeMax = 1.0; 
    double stepSize = 0.1; 

    for (double index = rangeMin; index <= rangeMax; index+= stepSize) 
    { 
     cout << index << endl; 
    } 
    cout << endl; 

    stepSize = 0.05; 
    for (double index = rangeMin; index <= rangeMax; index+= stepSize) 
    { 
     cout << index << endl; 
    } 

    return 0; 
} 

SALIDA

[email protected]:~/code/scratch$ ./a.out 
0 
0.1 
0.2 
0.3 
0.4 
0.5 
0.6 
0.7 
0.8 
0.9 
1 

0 
0.05 
0.1 
0.15 
0.2 
0.25 
0.3 
0.35 
0.4 
0.45 
0.5 
0.55 
0.6 
0.65 
0.7 
0.75 
0.8 
0.85 
0.9 
0.95 
[email protected]:~/code/scratch$ 
+0

puede ser un problema de coma flotante, ¡tengo que comprobarlo! –

+0

¡No acepte las respuestas de inmediato! Dé a las personas un tiempo para responder a su pregunta y escriba una respuesta. – GManNickG

Respuesta

16

Al utilizar valores de coma flotante no cada valor es exactamente representable, 0.95+0.05 > 1 porque 0.95 no es exactamente representable por un valor double.

Vea lo que Wikipedia tiene que decir sobre floating point accuracy.

Si nos fijamos en la IEEE floating point converter verá que el valor de 0.95 en coma flotante de 64 bits (double) es 0-01111111110-1110011001100110011001100110011001100110011001100110 introduciendo esto en un floating point calculator se obtiene el valor es 0.95000016 y añadiendo 0.05 a que se hace cargo de la 1.0 marca.

Es por esto que nunca debe usar puntos flotantes en bucles (o más generalmente comparar el resultado del cálculo de punto flotante con un valor exacto).

+0

En este ejemplo particular, una mejor solución podría ser calcular 'index' como un entero, y comparar' index * stepSize'. –

6

No debe usar == o < = para dobles debido a su representación interna. En el último paso obtendrás 0.95000000000000029. En su lugar se puede utilizar el siguiente código:

stepSize = 0.05; 
// stepSize/2 looks like a good delta for most cases 
for (double index = rangeMin; index < rangeMax+stepSize/2; index+= stepSize) 
{ 
    cout << index << endl; 
} 

Para más detalles, lea What Every Computer Scientist Should Know About Floating-Point Arithmetic.

2

Probablemente el último valor index sería como 1.00000001.

11

Generalmente, cuando compara los dobles, la comparación simple no es lo suficientemente buena, y debe compararlos "hasta una precisión". es decir:

if (fabs(double1-double2) < 0.0000001) { 
    do-something 
} 

El problema se produce debido a la representation of double variables.

1

Esto se debe a la representación inexacta de fracciones decimales por números de coma flotante. El tamaño de su paso no es realmente 0.1 o 0.05, es algún otro valor que está muy cerca. El ligero error se acumula a medida que avanzas en el ciclo.

Para resolver este problema, debe evitar la comparación de los números de coma flotante para la igualdad.

1

Ver esta salida: (punto flotante accuarcy)

#include <iostream> 
#include <iomanip> 
using namespace std; 
int main(){ 
    double rangeMin = 0.0; 
    double rangeMax = 1.0; 
    double stepSize = 0.1; 
    double index; 
    for (index = rangeMin; index <= rangeMax; index+=stepSize) 
     { 
       cout << fixed << setprecision(16) << index << endl; 
     } 
    cout << endl; 
    stepSize = 0.05; 
    for (index = rangeMin; index<= rangeMax; index+= stepSize) 
    { 
     cout << index << endl; 
      } 

    cout << "\n" << setprecision(16) << index << " " << rangeMax; 
    if(index==rangeMax) 
     cout << "\nEQ"; 
    else 
    cout << "\nNot EQ"; 
    return 0; 
} 

0.0000000000000000 
0.1000000000000000 
0.2000000000000000 
0.3000000000000000 
0.4000000000000000 
0.5000000000000000 
0.6000000000000000 
0.7000000000000000 
0.7999999999999999 
0.8999999999999999 
0.9999999999999999 

0.0000000000000000 
0.0500000000000000 
0.1000000000000000 
0.1500000000000000 
0.2000000000000000 
0.2500000000000000 
0.3000000000000000 
0.3500000000000000 
0.4000000000000000 
0.4500000000000000 
0.4999999999999999 
0.5499999999999999 
0.6000000000000000 
0.6500000000000000 
0.7000000000000001 
0.7500000000000001 
0.8000000000000002 
0.8500000000000002 
0.9000000000000002 
0.9500000000000003 

1.0000000000000002 1.0000000000000000 
Not EQ 
2

Como otros han dicho, no todos los números reales es exactamente representable como un valor de punto flotante, por lo que puede esperar un pequeño error de redondeo "al azar" en cálculos de coma flotante. Es similar a lo que ocurre con los dígitos decimales normales: 1/3 no es exactamente representable usando tres dígitos decimales (0.33), entonces (1/3) * 3 sería 0.99 y no exactamente 1.

Es posible utilizar algún tipo de "precisión" en sus comparaciones, pero recomendaría evitar los números de coma flotante para bucles, y en su lugar usar enteros.

Por ejemplo, el bucle

stepSize = 0.05; 
for (double index = rangeMin; index <= rangeMax; index+= stepSize) 
{ 
    cout << index << endl; 
} 

podría ser reemplazado por algo en la línea de

stepSize = 0.05; 
for (int index = 0; index < 21; ++index) 
{ 
    double value = rangeMin + index * stepSize; 
    cout << value << endl; 
} 
1

Como las respuestas anteriores, no es preciso utilizar no enteros en fines de bucles , Así que sugiero hacer como en el siguiente ejemplo para que mantenga la precisión de los enteros y puede obtener los decimales que desee:

#include<iostream> 
#include<cmath> 
#include<iomanip> 
using namespace std; 

int main() 
{ 
for (double y = 1; y!=10; y += 1) 
    cout << static_cast<double>(y/10) << endl; 



} 
Cuestiones relacionadas