En un programa C++, el símbolo de una potencia de 5 es
Recientemente, he visto muchas discusiones en el foro sobre la precisión de las variables de punto flotante en C++, así que explicaré en detalle cómo el procesador Intel maneja los números de punto flotante en caso de que estés confundido acerca de esto. problema. amigos como referencia. Para facilitar la explicación, aquí solo uso float como ejemplo. En términos de estructura de almacenamiento y algoritmo, double y float son iguales. La única diferencia es que float es de 32 bits y double es de 64 bits, por lo que double puede almacenar datos de mayor precisión. Otra cosa que decir es que el artículo y el programa son iguales y tienen un cierto rango de compatibilidad, por lo que si desea leer este artículo por completo, es mejor tener una comprensión más profunda de la conversión binaria, decimal y hexadecimal, y comprender la estructura de datos de almacenamiento en memoria y compilar un programa de consola simple usando VC.net. Bien, comencemos.
Todos sabemos que cualquier dato se almacena en la memoria en orden binario (1 o 0). Cada 1 o 0 se denomina bit. En una CPU x86, un byte tiene 8 bits. Por ejemplo, si el valor de una variable int corta de 16 bits (2 bytes) es 1156, su representación binaria es: 00000100 10000100. Dado que la CPU Intel tiene una estructura Little Endian (consulte Principios de informática), se almacena en orden de bytes inverso, por lo que debería verse así: 10000100 00000100, que es como se estructura el número de punto fijo 1156 en la memoria.
Entonces, ¿cómo se almacenan los números de punto flotante? Todos los compiladores C/C++ conocidos funcionan según la representación de punto flotante IEEE definida por el IEEE (Instituto Internacional de Ingenieros Eléctricos y Electrónicos). Esta estructura es una notación científica que utiliza un signo (positivo o negativo), un exponente y una mantisa, con la base determinada como 2, es decir, la representación de un número en coma flotante es la mantisa multiplicada por la potencia del exponente de 2 más el signo. . Echemos un vistazo a las especificaciones específicas de punto flotante:
flotante
*** 32 bits en total, equivalente a 4 bytes
Desde el bit más alto hasta Los bits más bajos son los bits 31, 30, 29, ... y 0.
El bit 31 es el bit de signo, donde 1 significa que el número es negativo, 0 significa que el número es negativo y viceversa. .
Los bits 30-23 y ****8 son bits exponentes.
Los dígitos 22-0 a ***23 son los dígitos de la mantisa.
Cada 8 bits se dividen en un grupo, que a su vez se divide en 4 grupos, a saber, Grupo A, Grupo B, Grupo C y Grupo D.
Cada grupo es un byte, almacenado en memoria en orden inverso, es decir: DCBA.
No consideraremos almacenarlos en orden inverso por el momento, porque eso confundiría al lector Totalmente confuso, así que primero los contaré en orden y luego les daré la vuelta al final.
Ahora, conviertamos paso a paso el número de punto flotante 12345.0f a código hexadecimal según la notación de punto flotante IEEE.
Cuando se trata de este tipo de número de punto flotante sin decimal, la parte entera se convierte directamente a representación binaria: 1 11100010 01000000 También se puede expresar así: 11110001001000000.0 Luego mueva el punto decimal hacia la izquierda, hasta el bit más alto. eso está a solo 1 lugar del bit más alto, además es el 1 más alto: 1.11100010010000000 un **** se mueve 16 bits En las operaciones booleanas, cada punto decimal movido hacia la izquierda es igual al exponente + 1 en el algoritmo científico. notación basada en 2, por lo que el número original es igual a este: 1.11100010010000000 * ( 2 ^ 16 ) Bien, ahora queremos que salgan tanto la mantisa como el exponente. Obviamente, el bit más alto siempre es 1, porque no se puede decir que comprar 16 huevos signifique comprar 0016 huevos, ¿verdad? (Oh, no me arrojes los huevos podridos que compraste ~) Entonces, ¿aún necesitamos conservar este 1? (Todos: ¡No es necesario!) Bien, entonces elimínalo. La mantisa binaria se convierte en 11100010010000000. Finalmente, se agregan 0 después de la mantisa hasta completar 23 dígitos: 11100010010000000000000 (MD, estos 0 casi me hacen perder los estribos~)
Veamos el índice nuevamente. **8 bits pueden representar un entero sin signo en el rango de 0 a 255, un entero sin signo en el rango de 0 a 255 o un entero sin signo en el rango de 0 a 255. El rango de enteros con signo es 0 - 255, o el rango de enteros con signo es -128 - 127. Pero debido a que el exponente puede ser un número negativo, para unificar el entero decimal en binario, primero se debe sumar 127. Aquí sumamos 16 a 127 para obtener 143, expresado en binario: 10001111
12345.0. f Este El número es positivo, por lo que el bit de signo es 0, por lo que lo combinamos según el formato de la lección anterior:
0 10001111 11100010010000000000000
01000111 11110001 00100000 00000000 p>
Luego lo convertimos a hexadecimal: 47 F1 20 00, y finalmente lo volteamos a: 00 20 F1 47:00 20 F1 47.
Ahora. ¡Puedes practicar tú mismo la conversión de 54321.0f en representación binaria!
Con la base anterior, permítanme ver un ejemplo usando decimales para ver por qué ocurren problemas de precisión.
Convierte el número de punto flotante 123.456f a código hexadecimal según la notación de punto flotante IEEE. Para tales números decimales, debe separar la parte entera de la parte decimal. La parte entera se expresa directamente en binario: 100100011. El procesamiento de la parte decimal es más problemático y no es fácil de explicar. El efecto opuesto puede ser mejor. Por ejemplo, si hay un decimal puro 0.57826, entonces 5 es el décimo y el orden es 1/10; es el centésimo lugar y el orden es 1/100; 8 es el milésimo lugar, el orden es 1/1000... y la relación de denominador de estos órdenes de bits es 10^1, 10^2, 10^3. .. Ahora supongamos que aquí En este caso, el orden de cada bit es 5, 7, 8, 2 y 6. Este decimal puro se puede expresar así: n = S1 * ( 1 / ( 10 ^ 1 ) ) + S2 * (1/(10^2))+ S3*(1/(10^3)) + ...+ Sn*(1/(10^n)).
Extender esta fórmula a la división b decimal pura es:
n = S1 * ( 1 / ( b ^ 1 ) ) + S2 * ( 1 / ( b ^ 2 ) ) + S3 * ( 1 / ( b ^ 3 ) ) + ... + Sn * ( 1 / ( b ^ n ) )
¡Dios mío, malditas matemáticas, ya casi soy profesor de matemáticas! No hay otra manera, para beneficio de la mayoría de los entusiastas de la programación, ¡bebe un poco de agua y continúa! Ahora, el decimal puro binario (como 0.100101011) debería entenderse mejor. La secuencia de orden de bits de este número es 1/(2^1), 1/(2^2), 1/(2^3), 1/(. 2 ^4), es decir, 0,5, 0,25, 0,125, 0,0625..... Multiplica los 1 o 0 en el orden S para encontrar cada término, luego súmalos para obtener el número original. Ahora su conocimiento básico debería ser suficiente. Volvamos a 0,45, un decimal puro. ¿Cómo expresar el acarreo? Ahora, cuando estés resolviendo problemas de matemáticas, es mejor no ver las respuestas primero, lo que ayudará a tu comprensión.
Supongo que no podías esperar a ver la respuesta porque te diste cuenta de que no iba a funcionar. Veamos los pasos: 1/2 ^ 1 bit (por conveniencia, solo se usa el índice 2 para representar el bit a continuación), el valor de bit 0.456 es menor que 0.5 es 0, el valor de bit 0.456 es mayor; que 0,25, el valor del bit es 1, y reste 0,25 de 0,45 a 0,206 para ingresar el siguiente bit, 0,206 es mayor que el valor del bit de 0,125, este bit es 1 y reste 0,125 de 0,206 para obtener 0,081; dígitos, 0.206 es mayor que el valor del bit de 0.125, este bit es 1, 0.206 menos 0.125 obtiene 0.081 e ingresa el siguiente dígito, si 0.081 es mayor que 0.0625, este dígito es 1, 0.0625 menos 0.081 obtiene 0.0185; ingresa el siguiente dígito; 5 dígitos, si 0.0185 es menor que 0.03125, este dígito es 0. ……. Pueden surgir problemas incluso si se excede la longitud máxima de bits de mantisa (23 bits). Excepto el infinito, que se conoce como el problema de precisión con números de coma flotante. Sin embargo, no estoy aquí para hablarles sobre los "cálculos numéricos" ni para utilizar varios métodos para mejorar la precisión de los cálculos, porque es demasiado complicado, me temo que no podría resolverlo incluso si hablé de ello el año pasado. Sólo hablaré de números de coma flotante y me detendré aquí.
Bien, continuemos. ¿Dónde dije eso? Ah, es cierto, ese número aún no se ha transferido. De todos modos, al final ya está agotado. Agregar la parte entera anterior es suficiente para calcular 24 dígitos: 1111011.01110100101111001. Un BC preguntó: "¿No son 23 dígitos?" Yo: "¿No dije que quitaras el primer 1? ¡Por supuesto que tienes que agregar un dígito!". Ahora comienza a mover el punto decimal hacia la izquierda, todos me siguen. : "1, 2, 3 ..." Está bien, un dios se ha movido 6 bits, 6 más 127 a 131 (¿cómo enseñar así a los estudiantes de primaria?), El binario no es 23. Oh ~), representación binaria: 10000101, el bit de signo es..., no diré más..., cuanto más hablo de ello, más difícil es hablar, así que puedes verlo por ti mismo:
0 10000101 11101101110100101111001
42 F6 E9 79
79 E9 F6 42
Aquí se explica cómo convertir decimal puro a hexadecimal. Para decimal puro, como 0.0456, necesitamos normalizarlo a 1.xxxx * (2 ^ n) y usar la siguiente fórmula para encontrar el n correspondiente al decimal puro X:
n = int( 1 + log (2)X );
0,0456 se puede expresar como 1,4592 multiplicado por 2 elevado a la potencia de -5, es decir, 1,4592 * ( 2 ^ -5 ).
Después de convertir a este formulario, podemos realizar la operación de acuerdo con el proceso del segundo ejemplo anterior:
1.
-5 + 127 = 122
0 01111010 01110101100011100010001
Finalmente:
11 C7 3A 3D
Una cosa más que debo mencionar es que el valor hexadecimal correspondiente de 0.0f es 00 00 00 00, recuerde esto.
Finalmente, publique una función que pueda analizar y generar la estructura de punto flotante del código fuente. Si está interesado, compruébelo usted mismo:
//Ingrese 4 bytes de punto flotante. datos de memoria
void DecodeFloat( BYTE pByte[4] )
{
printf( "Sin formato (decimal): %d %d %d %d\ n" , (int)pByte[0],
(int)pByte[1], (int)pByte[2], (int)pByte[3] );
printf( " Voltear (decimal):%d %d %d %d\n" , (int)pByte[3],
(int)pByte[2], (int)pByte[1] , (int )pByte[0] );
bitset<32> bitAll( *(ULONG*)pByte
string strBinary = bitAll.to_ string
strBinary.insert( 9, " "
strBinary.insert( 1, " " ); p> cout << "Binario:" << strBinary.c_str() < "Binario:" << "Binario.c_str() << endl;
cout << "Símbolo:" << ( bitTodo[31 ] ? "-" :"
bitet<32> bitTemp;
bitTemp = bitAll;
bitTemp <<= 1;
LONG ulExponent = 0;
for ( int i = 0; i < 8; i++ )
{
ulExponent |= ( bitTemp[ 31 - i ] << ( 7 - i );
}
ulExponent -= 127;
cout << " Exponente (decimal):" < < ulExponent < < endl
bitTemp = bitAll;
bitTemp <<= 9; for ( int i = 0; i < 23; i++ )
{
bool b = bitTemp[ 31 - i ]
fMantissa += ( ( float)bitTemp [ 31 - i ] / (float)( 2 << i )
}
cout = 1.
cout & lt; < "Mantissa (decimal):" << fMantissa << endl;
float fPow
if ( ulExponent;
>= 0 )
{
fPow = (flotante)( 2 << ( ulExponent - 1 )
}
); else
{
fPow = 1.0f / (flotante)( 2 << ( -1 - ulExponent )
}
}
cout << "Resultado de la operación:" < ;< fMantissa * fPow << endl
}
}
Después del cansancio, me di cuenta de que este post, aunque breve, era el más difícil de escribir. Dios mío, no soy una computadora, pero ¿por qué todo lo que veo son 1 y 0? Parece que me estoy convirtiendo en un observador de Matrix. ...¡Espero que puedas estar a la altura de mi arduo trabajo y ayudarme!