2010-09-24 16 views
5

Considere el siguiente fragmento de código:¿Cómo manejan printf y scanf los formatos de precisión de coma flotante?

float val1 = 214.20; 
double val2 = 214.20; 

printf("float : %f, %4.6f, %4.2f \n", val1, val1, val1); 
printf("double: %f, %4.6f, %4.2f \n", val2, val2, val2); 

que da salida:

float : 214.199997, 214.199997, 214.20 | <- the correct value I wanted 
double: 214.200000, 214.200000, 214.20 | 

entiendo que 214.20 tiene una representación binaria infinita. Los dos primeros elementos de la primera línea tienen una aproximación del valor previsto, pero el último parece tener ninguna aproximación en absoluto, y esto me llevó a la siguiente pregunta:

Cómo hacer el scanf, fscanf, printf, fprintf (etc.) funciones tratan los formatos de precisión?

Sin precisión proporcionada, printf imprimió un valor aproximado, pero con %4.2f dio el resultado correcto. ¿Puede explicarme el algoritmo utilizado por estas funciones para manejar la precisión?

Respuesta

10

El problema es que 214.20 no puede expresarse exactamente con representación binaria. Pocos números decimales pueden. Entonces una aproximación es almacenada. Ahora, cuando usa printf, la representación binaria se convierte en una representación decimal, pero nuevamente no se puede expresar exactamente y solo se aproxima.

Como habrás notado, puedes dar una precisión a printf para indicarle cómo redondear la aproximación decimal. Y si no le da una precisión, se supone una precisión de 6 (consulte la página de manual para obtener más información).

Si utiliza %.40f para el flotador y %.40lf por el doble de su ejemplo anterior, se obtiene estos resultados:

214.1999969482421875000000000000000000000000 
214.1999999999999886313162278383970260620117 

Son diferentes porque con doble, hay más bits a una mejor aproximación 214.20. Pero como puede ver, todavía son muy raros cuando se representan en decimales.

Recomiendo leer el Wikipedia article on floating point numbers para obtener más información sobre cómo funcionan los números de punto flotante. Una lectura excelente también es What Every Computer Scientist Should Know About Floating-Point Arithmetic

+0

Gracias por responder, aunque mi pregunta todavía está en el mismo lugar. En primer lugar, el flotador NO podría contener el valor exacto de 214.20, entonces, ¿cómo podría recuperarlo SÓLO con precisión de% 4.2f y NO con% 4.6f? ¿Qué hizo para recuperar el valor? Entiendo que la aproximación está hecha, pero ¿CUÁL FUE EL PATRÓN UTILIZADO QUE CAUSADO INTERNO ALMACENADO 214.199997 PARA CONVERTIRSE EN 214.20 Y CÓMO LLEGAMOS A ESO? –

+0

No estoy seguro de que te entiendo, pero eso es solo el [redondeo] (http://en.wikipedia.org/wiki/Rounding). Es pura casualidad que '% 4.2f' te brinde exactamente el valor que deseas. Todavía está "mal" ya que la representación binaria es * no * equivalente a 214.20 pero a 214.1999969482421875. Ahora, si redondeas "214.1999969482421875" a 6 dígitos, obtienes "214.199997" porque el séptimo dígito es un 9, convirtiendo el 6 en un 7. Pero cuando solo redondeas a 2 dígitos, obtienes "214.20" porque el tercer dígito es un nueve, por lo que necesita aumentar el 9 en el segundo dígito. Eso "desborda" a 10, convirtiendo el ".19" en ".20". – DarkDust

+0

¡¡Oh !! Creo que está tratando de decir que esta operación se realiza en representación decimal, y así se formó 214.199997 como un efecto redondo de doble precisión. Porque si hubiera estado en representación binaria ya hemos confirmado que 214.20 NO es representable en binario, con todas las habilidades de redondeo utilizadas. –

1

scanf redondeará el valor de entrada al valor de coma flotante exactamente exacto más cercano. Como la respuesta de DarkDust ilustra, en precisión simple, el valor representable exactamente más cercano está por debajo del valor exacto, y en precisión doble, el valor más exacto exactamente representable está por encima del valor exacto.

+0

Si fuera posible, ¿por qué almacenamos la aproximación en primer lugar? :-). Disculpe si estoy causando dolor, pero eso es lo que quería saber, lo que hace Scanf para representar un valor que se dio en seis lugares después del decimal para convertirse en dos puntos después del decimal. Dado que .20 NO es representable en el punto flotante binario –

+0

Parece que esta pregunta de seguimiento se responde en el cuadro de diálogo de comentarios con DarkDust. –

4

Dado que usted preguntó por scanf, una cosa que debe tener en cuenta es que POSIX requiere printf y una posterior scanf (o strtod) para reconstruir el valor original dígitos exactamente, siempre y cuando lo suficientemente significativas (al menos DECIMAL_DIG, creo) fueron impresos Claro C por supuesto no hace tal requisito; el estándar C prácticamente permite que las operaciones en coma flotante den el resultado que quiera el implementador, siempre que lo documenten. Sin embargo, si su intención es almacenar números de coma flotante en archivos de texto para leerlos más tarde, es mejor que utilice el especificador C99 %a para imprimirlos en hexadecimal. De esta forma serán exactos y no hay confusión sobre si el proceso de serialización/deserialización pierde precisión.

Tenga en cuenta que cuando dije "reconstruir el valor original", quiero decir el valor real que se mantuvo en la variable/expresión pasada a printf, no el decimal original que escribió en el archivo fuente que se redondeó al mejor representación binaria por el compilador.

Cuestiones relacionadas