2010-03-24 20 views
7

Parece el tipo de código que solo falla in situ, pero intentaré adaptarlo a un fragmento de código que represente lo que estoy viendo.¿Por qué mi número se redondea incorrectamente?

float f = myFloat * myConstInt; /* Where myFloat==13.45, and myConstInt==20 */ 
int i = (int)f; 
int i2 = (int)(myFloat * myConstInt); 

Después de recorrer el código, i == 269 e i2 == 268. ¿Qué está pasando aquí para explicar la diferencia?

+3

Potencial para una pregunta de entrevista perversa: ¿Cuáles son los valores de i e i2? –

+0

... y aquí hay un manual de IEEE y algo de información de registro de la máquina. Y un marcador de pizarra. ¡Ir! –

+2

Vea también http://stackoverflow.com/questions/2342396 y http://stackoverflow.com/questions/2345534 y http://stackoverflow.com/questions/2225503 y http://stackoverflow.com/questions/2494724 –

Respuesta

15

La matemática flotante se puede realizar con mayor precisión que la anunciada. Pero tan pronto como lo almacene en float f, esa precisión adicional se pierde. No perderás esa precisión en el segundo método hasta que, por supuesto, arrojes el resultado a int.

Editar: Ver esta pregunta Why differs floating-point precision in C# when separated by parantheses and when separated by statements? para una mejor explicación de lo que probablemente proporcionado.

+0

+1 buena explicación. – technophile

+0

segundo resultado no es correcto! entonces la precisión se pierde allí. ¿pero donde? – Andrey

+1

Consulte esta pregunta: http://stackoverflow.com/questions/2491161/why-differs-floating-point-precision-in-c-when-separated- by-parantheses-and-when/2494724#2494724 para una mejor explicación que probablemente proporcioné –

4

Dado que las variables de coma flotante no son infinitely accurate. Use un decimal si necesita ese tipo de precisión.

Diferente rounding modes también puede tener este problema, pero el problema de precisión es el que se está encontrando aquí, AFAIK.

+0

+1 para los enlaces interesantes – Robusto

+1

Um, los decimales tampoco son infinitamente precisos. –

+1

@Daniel: los decimales no son infinitamente precisos, pero son * consistentemente * precisos. La aritmética decimal se realiza en números enteros, no en flotadores, por lo que no tiene precisión en función de la calidad del chip. –

1

Reemplazar con

double f = myFloat * myConstInt; 

y ver si le da la misma respuesta.

+0

Si bien puede dar el resultado "correcto" en este caso, los dobles mostrarán el mismo problema en algunos casos. Necesita un tipo de precisión arbitraria para evitar el problema. – technophile

2

El punto flotante tiene una precisión limitada, y se basa en binario en lugar de decimal. El número decimal 13.45 no se puede representar con precisión en el punto flotante binario, por lo que redondea hacia abajo. La multiplicación por 20 exagera aún más la pérdida de precisión. En este punto, tiene 268.999 ... - no 269 - por lo tanto, la conversión a entero se trunca en 268.

Para redondear al número entero más cercano, puede intentar agregar 0.5 antes de volver a convertirlo en entero.

para la aritmética "perfecta", se podría tratar de usar un decimal o numérico racional - Creo que C# tiene bibliotecas para ambos, pero no estoy seguro. Sin embargo, estos serán más lentos.

EDIT - He encontrado un tipo "decimal" hasta el momento, pero no es un racional - Puedo estar equivocado acerca de que esté disponible. El punto flotante decimal es inexacto, al igual que el binario, pero es el tipo de inexactitud al que estamos acostumbrados, por lo que da resultados menos sorprendentes.

1

Me gustaría ofrecer una explicación diferente.

Aquí está el código, que he anotado (Miré en la memoria para diseccionar los flotadores):

 
float myFloat = 13.45; //In binary is 1101.01110011001100110011 
int myConstInt = 20; 
float f = myFloat * myConstInt; //In binary is exactly 100001101 (269 decimal) 
int i = (int)f; // Turns float 269 into int 269 -- no surprises 
int i2 = (int)(myFloat * myConstInt);//"Extra precision" causes round to 268 

Veamos más de cerca a los cálculos:

  • f = 1101,01110011001100110011 * 10100 = 100001100.111111111111111 111

    La parte después del espacio son los bits 25-27, que hacen que se redondee el bit 24, y por lo tanto, el valor completo se redondeará a 269

  • int i2 = (int) (myFloat * myConstInt)

    myfloat se extiende a doble precisión para el cálculo (0s se anexan): 1101.0111001100110011001100000000000000000000000000000

    myfloat * 20 = 100001100,11111111111111111100000000000000000000000000

    Bits 54 y más allá son 0s, así que no hay redondeo se realiza: los resultados emitidos en el número entero 268.

    (Una explicación similar funcionaría si se utiliza precisión extendido .)

ACTUALIZACIÓN: refiné mi respuesta y escribió un artículo en toda regla llamada When Floats Don’t Behave Like Floats

Cuestiones relacionadas