imprimiendo la parte integral de un número de punto flotante

Estoy tratando de averiguar cómo imprimir números de punto flotante sin usar las funciones de la biblioteca. La impresión de la parte decimal de un número de punto flotante resultó ser bastante fácil. Imprimir la parte integral es más difícil:

static const int base = 2; static const char hex[] = "0123456789abcdef"; void print_integral_part(float value) { assert(value >= 0); char a[129]; // worst case is 128 digits for base 2 plus NUL char * p = a + 128; *p = 0; do { int digit = fmod(value, base); value /= base; assert(p > a); *--p = hex[digit]; } while (value >= 1); printf("%s", p); } 

La impresión de la parte integral de FLT_MAX funciona perfectamente con la base 2 y la base 16:

 11111111111111111111111100000000000000000000000000000000000000000000000000000000 000000000000000000000000000000000000000000000000 (base 2) ffffff00000000000000000000000000 (base 16) 

Sin embargo, la impresión en base 10 genera errores después de los primeros 7 dígitos:

 340282368002860660002286082464244022240 (my own function) 340282346638528859811704183484516925440 (printf) 

Supongo que este es un resultado de la división por 10. Se mejora si uso el doble en lugar del flotador:

 340282346638528986604286022844204804240 (my own function) 340282346638528859811704183484516925440 (printf) 

(Si no cree en printf , ingrese 2^128-2^104 en Wolfram Alpha. Es correcto).

Ahora, ¿cómo logra printf imprimir el resultado correcto? ¿Utiliza algunas instalaciones bigint internamente? ¿O hay algún truco de punto flotante que me estoy perdiendo?

Parece que el caballo de trabajo para la conversión de flotación a cadena es la función dtoa() . Vea dtoa.c en newlib para saber cómo lo hacen.

Ahora, ¿cómo logra printf imprimir el resultado correcto?

Creo que está cerca de la magia. Al menos la fuente parece algún tipo de conjuro oscuro.

¿Utiliza algunas instalaciones bigint internamente?

Sí, busque _Bigint en el archivo fuente vinculado.

¿O hay algún truco de punto flotante que me estoy perdiendo?

Probable.

Creo que el problema radica en el valor / = base; No olvide que 10 no es una fracción finita en el sistema binario y, por lo tanto, este cálculo nunca es correcto. También asumo que algún error ocurrirá en fmod debido a la misma razón.

printf primero calculará la parte integral y luego la convertirá a decimal (si obtengo la forma en que imprime la parte integral correctamente).

/ Edición: Lea primero la respuesta de Unni . Estos resultados provienen de http://codepad.org/TLqQzLO3 .

 void print_integral_part(float value) { printf("input : %f\n", value); char a[129]; // worst case is 128 digits for base 2 plus NUL char * p = a + 128; *p = 0; do { int digit = fmod(value, base); value /= base; printf("interm: %f\n", value); *--p = hex[digit]; } while (value >= 1); printf("result: %s\n", p); } print_integral_part(3.40282347e+38F); 

para ver cómo se ensucia su valor con la operación value /= base :

 input : 340282346638528859811704183484516925440.000000 interm: 34028234663852885981170418348451692544.000000 interm: 3402823466385288480057879763104038912.000000 interm: 340282359315034876851393457419190272.000000 interm: 34028234346940236846450271659753472.000000 interm: 3402823335658820218996583884128256.000000 interm: 340282327376181848531187106054144.000000 interm: 34028232737618183051678859657216.000000 interm: 3402823225404785588136713388032.000000 interm: 340282334629736780292710989824.000000 interm: 34028231951816403862828351488.000000 interm: 3402823242405304929106264064.000000 interm: 340282336046446683592065024.000000 interm: 34028232866774907300610048.000000 interm: 3402823378911210969759744.000000 interm: 340282332126513595416576.000000 interm: 34028233212651357863936.000000 interm: 3402823276229139890176.000000 interm: 340282333252413489152.000000 interm: 34028234732616232960.000000 interm: 3402823561222553600.000000 interm: 340282356122255360.000000 interm: 34028235612225536.000000 interm: 3402823561222553.500000 interm: 340282366859673.625000 interm: 34028237357056.000000 interm: 3402823735705.600098 interm: 340282363084.799988 interm: 34028237619.200001 interm: 3402823680.000000 interm: 340282368.000000 interm: 34028236.800000 interm: 3402823.600000 interm: 340282.350000 interm: 34028.234375 interm: 3402.823438 interm: 340.282349 interm: 34.028235 interm: 3.402824 interm: 0.340282 result: 340282368002860660002286082464244022240 

En caso de duda, tirar más printfs en él;)

De acuerdo con la implementación de flotación de precisión simple IEEE, solo 24 bits de datos se almacenan en cualquier momento en una variable flotante. Esto significa que solo se almacenan un máximo de 7 dígitos decimales en el número flotante.

El rest de la enormidad del número se almacena en el exponente. FLT_MAX se inicializa como 3.402823466e + 38F. Entonces, después de la décima precisión, el dígito que debe imprimirse no se define en ninguna parte.

Del comstackdor de Visual C ++ 2010, obtengo esta salida 3402823466385288600000000000000000000.000000, que es la única salida válida.

Entonces, inicialmente tenemos estos muchos dígitos válidos 3402823466 Así que después de la 1ª división solo tenemos 0402823466 Por lo tanto, el sistema necesita deshacerse del 0 izquierdo e introducir un nuevo dígito a la derecha. En la división de enteros ideal, es 0. Debido a que está haciendo una división flotante (valor / = base;), el sistema está obteniendo algún otro dígito para completar esa ubicación.

Entonces, en mi opinión, printf podría estar asignando los dígitos significativos arriba disponibles a un entero y trabajar con esto.

Vamos a explicar esto una vez más. Después de que la parte entera se haya impreso (exactamente) sin ningún redondeo que no sea cortar hacia 0, es el momento de los bits decimales.

Comience con una cadena de bytes (por ejemplo, 100 para empezar) que contienen ceros binarios. Si se establece el primer bit a la derecha del punto decimal en el valor de fp, eso significa que 0.5 (2 ^ -1 o 1 / (2 ^ 1) es un componente de la fracción. Por lo tanto, agregue 5 al primer byte. Si el siguiente bit se establece en 0.25 (2 ^ -2 o 1 / (2 ^ 2)) es parte de la fracción. Agregue 5 al segundo byte y agregue 2 al primero (oh, no olvide el acarreo, ocurren) matemáticas de la escuela inferior). El siguiente conjunto de bits significa 0.125, así que agregue 5 al tercer byte, 2 al segundo y 1 al primero. Y así sucesivamente:

  value string of binary 0s start 0 0000000000000000000 ... bit 1 0.5 5000000000000000000 ... bit 2 0.25 7500000000000000000 ... bit 3 0.125 8750000000000000000 ... bit 4 0.0625 9375000000000000000 ... bit 5 0.03125 9687500000000000000 ... bit 6 0.015625 9843750000000000000 ... bit 7 0.0078125 9921875000000000000 ... bit 8 0.00390625 9960937500000000000 ... bit 9 0.001953125 9980468750000000000 ... ... 

Hice esto a mano, así que tal vez me haya perdido algo, pero implementar esto en el código es trivial.

Por lo tanto, para todos aquellos SO “no se puede obtener un resultado exacto usando float” las personas que no saben de qué están hablando aquí son una prueba de que los valores de fracción de punto flotante son perfectamente exactos. Extremadamente exacto. Pero binario.

Para aquellos que se toman el tiempo de entender cómo funciona esto, una mejor precisión está al scope de la mano. En cuanto a los demás … bueno, supongo que seguirán sin navegar por los foros en busca de la respuesta a una pregunta que ha sido respondida varias veces anteriormente, honestamente creen que han descubierto “punto flotante roto” (o como se llame) y publicar una nueva variante de la misma pregunta todos los días.

“Cerca de la magia”, “conjuro oscuro” – ¡eso es muy gracioso!

Al igual que la respuesta de Agent_L, está sufriendo el resultado falso causado al dividir el valor entre 10. La flotación, como cualquier tipo de punto flotante binario, no puede express correctamente el número más racional en decimal. Después de la división, la mayoría de los casos el resultado no puede ajustarse en binario, por lo que se redondeará. Por lo tanto, cuanto más se divida, más error se dará cuenta.

Si el número no es muy grande, una solución rápida sería multiplicarlo por 10 o una potencia de 10, dependiendo de cuántos dígitos después del punto decimal necesite.

Otra forma fue descrita aquí

Este progtwig funcionará para usted.

 #include int main() { float num; int z; scanf("%f",&num); z=(int)num; printf("the integral part of the floating point number is %d",z); }