9

que tienen esta simple línea de código:¿Por qué la variable flotante ahorra valor al cortar los dígitos después del punto de una manera extraña?

float val = 123456.123456; 

Al imprimir este val o buscar en su alcance, que almacena el valor 123456,13

Ok, está bien, no puede almacenar todos esos dígitos después del punto justo en 4 bytes, pero ¿por qué hace 13 después del punto? ¿No debería ser 12?

(usando VC++ 2010 Express en win32)

+0

gracias a todos por las respuestas. lo entiendo ahora. solo pensé que debería soltar dígitos sobredimensionados después del punto, en lugar de redondearlo. – Kosmos

Respuesta

7

Cuando se representa como un flotante, su número tiene un exponente de 16 (es decir, el valor es su mantisse multiplicado por 2^16 o 65536). El Mantisse se convierte entonces en

123456.123456/65536 = 1.8837909462890625 

el fin de encajar en un flotador 32 bits, el Mantisse se trunca a 23 bits, por lo que ahora se convierte en 1.883791. Cuando se multiplica por 65536, se convierte en 123456.125.

Observe el 5 en la tercera posición después del punto decimal: la rutina de salida de C++ que utilizó lo redondea, haciendo que su número final se vea como 123456.13.

EDITAR Explicación del redondeo: (comentario de Rick Regan)

El redondeo se produce primero en binario (24 bits), en decimal a la conversión binaria, y luego a decimal, en printf. El valor almacenado es 1.1110001001000000001 x 2^16 = 1.8837909698486328125 x 2^16 = 123456.125. Se imprime como 123456.13, pero solo porque Visual C++ utiliza el redondeo "redondeado a la mitad de cero".

Rick tiene un outstanding article on the subject, también.

Si desea jugar con otros números y sus representaciones de flotación, aquí hay un very useful IEEE-754 calculator.

+0

Podría explicar un poco más ... No pude entender particularmente esta línea "Cuando se representa como un flotador, su número tiene un exponente de 16" . Por qué es necesario? –

+0

@RasmiRanjanNayak El flotador de 32 bits se representa como un mantisse de 23 bits, un exponente binario de siete bits y un bit de signo. Lógicamente, divide su número original por dos e incrementa el exponente hasta que el único dígito restante delante del punto decimal es '1'. Para '123456.123456', se requieren 16 divisiones. – dasblinkenlight

+3

@dasblinkenlight En el mejor de los casos, esta descripción es engañosa. El redondeo se produce primero en formato binario (a 24 bits), en conversión de decimal a binario, y luego a decimal, en printf. El valor almacenado es 1.1110001001000000001 x 2^16 = 1.8837909698486328125 x 2^16 = 123456.125. Se imprime como 123456.13, pero solo porque Visual C++ utiliza el redondeo "redondeado a la mitad de cero" (consulte mi artículo http://www.exploringbinary.com/inconsistent-rounding-of-printed-floating-point-numbers/.) –

2

intente imprimir el valor de std::numeric_limits<float>::digits10. Esto es, en términos generales, la precisión en la base 10 que tiene un flotador. Está tratando de excederlo, por lo que está experimentando una pérdida de precisión (lo que significa que los dígitos más allá de los significativos no son realmente significativos).

Véase p. What is the meaning of numeric_limits<double>::digits10

2

Depende totalmente del compilador. Verifíquelo en GCC. Debe ser xxx.12

+5

Si el formato utilizado internamente es IEEE-754, no debería depender del compilador. –

+0

Ver http://steve.hollasch.net/cgindex/coding/ieeefloat.html –

+0

Creo que estamos dando "compilador-dependiente" dos significados diferentes; Lo que yo, y otros, entendí es que estás diciendo que "es normal que varíe de compilador a compilador". ¿Estás diciendo que es un * error específico del compilador *? –

8

El valor almacenado en val es igual a 123456.125. Que está recibiendo .13 porque estás redondeo:

float val = 123456.123456; 
printf("%.4f %.2f\n", val, val); 

de salida: 123456.1250 123456.13

Se debe utilizar doble en este caso para evitar el truncamiento. El compilador también debe advertirle: "advertencia C4305: 'inicialización': truncamiento de 'doble' a 'flotar'".

10

En binario, 123456.123456 es 11110001001000000.000111111001 ... (infinito). Redondea al 11110001001000000.001, o 123456.125. Eso redondea a 123456.13 cuando se imprime.

Cuestiones relacionadas