c++ - datos - firmar cambios al pasar de int a float y viceversa
convertir string a int c++ (2)
La respuesta de Pascal está bien, pero le faltan detalles que implican que algunos usuarios no la entienden ;-). Si está interesado en cómo se ve en el nivel inferior (suponiendo que el coprocesador y no el software maneje operaciones de punto flotante), siga leyendo.
En 32 bits de flotante (IEEE 754) puede almacenar todos los enteros dentro del rango [-2 24 ... 2 24 ] . Los enteros fuera del rango también pueden tener una representación exacta como float pero no todos tienen. El problema es que puedes tener solo 24 bits significativos para jugar en float.
Así es como la conversión de int-> float generalmente se ve en un nivel bajo:
fild dword ptr[your int]
fstp dword ptr[your float]
Es solo una secuencia de 2 instrucciones de coprocesador. Primero carga 32 bits int en la pila del comprobador y lo convierte en un flotador de 80 bits de ancho.
Intel® 64 y IA-32 Architectures Software Developer''s Manual
(PROGRAMACIÓN CON LA FPU X87):
Cuando los valores enteros de coma flotante, entero o BCD empaquetados se cargan desde la memoria en cualquiera de los registros de datos de FPU x87, los valores se convierten automáticamente en formato de coma flotante de precisión extendida doble (si aún no están en ese formato).
Como los registros FPU son flotadores de 80 bits de ancho, no hay problema con fild
aquí ya que 32bit int encaja perfectamente en el significado de 64 bits del formato de coma flotante.
Hasta aquí todo bien.
La segunda parte - fstp
es un poco complicado y puede ser sorprendente. Se supone que debe almacenar un punto flotante de 80 bits en un flotador de 32 bits. Aunque se trata de valores enteros (en la pregunta), el coprocesador en realidad puede realizar ''redondeo''. Ke? ¿Cómo se redondea el valor entero incluso si se almacena en formato de coma flotante? ;-).
Lo explicaré en breve: veamos primero qué modos de redondeo x87 proporciona (son la encarnación de los modos de redondeo IEE 754). X87 fpu tiene 4 modos de redondeo controlados por los bits # 10 y # 11 de la palabra de control de fpu:
- 00 - hasta el más cercano - El resultado redondeado es el más cercano al resultado infinitamente preciso. Si dos valores son igualmente cercanos, el resultado es el valor par (es decir, el que tiene el bit menos significativo de cero). Defecto
- 01 - hacia -Inf
- 10 - hacia + inf
- 11 - hacia 0 (es decir, truncado)
Puede jugar con los modos de redondeo usando este código simple (aunque puede hacerlo de manera diferente, mostrando el nivel bajo aquí):
enum ROUNDING_MODE
{
RM_TO_NEAREST = 0x00,
RM_TOWARD_MINF = 0x01,
RM_TOWARD_PINF = 0x02,
RM_TOWARD_ZERO = 0x03 // TRUNCATE
};
void set_round_mode(enum ROUNDING_MODE rm)
{
short csw;
short tmp = rm;
_asm
{
push ax
fstcw [csw]
mov ax, [csw]
and ax, ~(3<<10)
shl [tmp], 10
or ax, tmp
mov [csw], ax
fldcw [csw]
pop ax
}
}
Bien, pero ¿cómo está eso relacionado con los valores enteros? Paciencia ... para comprender por qué es posible que necesite modos de redondeo que impliquen la conversión de flotador int a flotante, compruebe la forma más obvia de convertir int a flotante - truncamiento (no predeterminado) - que puede verse así:
- signo de registro
- negar su int si es menor que cero
- encontrar la posición de la izquierda 1
- desplazar int a la derecha / izquierda para que 1 encontrado arriba esté posicionado en el bit # 23
- registre el número de turnos durante el proceso para que pueda calcular el exponente
Y el código que simula este comportamiento puede verse así:
float int2float(int value)
{
// handles all values from [-2^24...2^24]
// outside this range only some integers may be represented exactly
// this method will use truncation ''rounding mode'' during conversion
// we can safely reinterpret it as 0.0
if (value == 0) return 0.0;
if (value == (1U<<31)) // ie -2^31
{
// -(-2^31) = -2^31 so we''ll not be able to handle it below - use const
value = 0xCF000000;
return *((float*)&value);
}
int sign = 0;
// handle negative values
if (value < 0)
{
sign = 1U << 31;
value = -value;
}
// although right shift of signed is undefined - all compilers (that I know) do
// arithmetic shift (copies sign into MSB) is what I prefer here
// hence using unsigned abs_value_copy for shift
unsigned int abs_value_copy = value;
// find leading one
int bit_num = 31;
int shift_count = 0;
for(; bit_num > 0; bit_num--)
{
if (abs_value_copy & (1U<<bit_num))
{
if (bit_num >= 23)
{
// need to shift right
shift_count = bit_num - 23;
abs_value_copy >>= shift_count;
}
else
{
// need to shift left
shift_count = 23 - bit_num;
abs_value_copy <<= shift_count;
}
break;
}
}
// exponent is biased by 127
int exp = bit_num + 127;
// clear leading 1 (bit #23) (it will implicitly be there but not stored)
int coeff = abs_value_copy & ~(1<<23);
// move exp to the right place
exp <<= 23;
int ret = sign | exp | coeff;
return *((float*)&ret);
}
Ahora ejemplo: el modo de truncamiento convierte 2147483583
a 2147483520
.
2147483583 = 01111111_11111111_11111111_10111111
Durante la conversión int-> float, debe desplazar el extremo izquierdo 1 al bit # 23. Ahora el líder 1 está en el bit # 30. Para ubicarlo en el bit # 23 debes realizar el cambio a la derecha en 7 posiciones. Durante ese tiempo, perderá (no cabrán en el formato de flotación de 32 bits) 7 bits de lsb de la derecha (truncará / cortará). Ellos eran:
01111111 = 63
Y 63 es el número original perdido:
2147483583 -> 2147483520 + 63
Truncar es fácil, pero puede no ser necesariamente lo que quiere y / o es mejor para todos los casos. Considere el siguiente ejemplo:
67108871 = 00000100_00000000_00000000_00000111
El valor superior no se puede representar exactamente mediante float, pero compruebe qué truncamiento le hace. Como anteriormente, necesitamos desplazar el extremo izquierdo de 1 a bit # 23. Esto requiere que el valor se desplace exactamente 3 posiciones perdiendo 3 bits LSB (a partir de ahora escribiré los números de forma diferente mostrando dónde está implícito el 24º bit de float y pondré 23 bits explícitos de significado):
00000001.[0000000_00000000_00000000] 111 * 2^26 (3 bits shifted out)
El truncamiento corta 3 bits de seguimiento, dejándonos con 67108864
(67108864 + 7 (3 bits picados)) = 67108871 (recuerde que aunque desplazamos, compensamos con la manipulación del exponente, omitida aquí).
¿Es eso lo suficientemente bueno? Hola, 67108872
es perfectamente representable por flotador de 32 bits y debería ser mucho mejor que 67108864
¿verdad? CORRECTO y aquí es donde es posible que desee hablar de redondeo al convertir int a flotador de 32 bits.
Ahora veamos cómo funciona el modo predeterminado ''redondeando al más cercano par'' y cuáles son sus implicaciones en el caso de OP. Considera el mismo ejemplo una vez más.
67108871 = 00000100_00000000_00000000_00000111
Como sabemos, necesitamos 3 cambios a la derecha para ubicar a la izquierda 1 en el bit # 23:
00000000_1.[0000000_00000000_00000000] 111 * 2^26 (3 bits shifted out)
El procedimiento de ''redondear al par más cercano'' implica encontrar 2 números que 67108871
valor de entrada 67108871
desde abajo y arriba lo más cerca posible. Tenga en cuenta que todavía operamos dentro de FPU en 80bits, por lo que aunque muestre que algunos bits se han desplazado, todavía están en el registro de FPU, pero se eliminarán durante la operación de redondeo cuando se almacena el valor de salida.
00000000_1.[0000000_00000000_00000000] 111 * 2^26 (3 bits shifted out)
2 valores que 00000000_1.[0000000_00000000_00000000] 111 * 2^26
estrechamente con 00000000_1.[0000000_00000000_00000000] 111 * 2^26
son:
Desde arriba:
00000000_1.[0000000_00000000_00000000] 111 * 2^26
+1
= 00000000_1.[0000000_00000000_00000001] * 2^26 = 67108872
y de abajo:
00000000_1.[0000000_00000000_00000000] * 2^26 = 67108864
Obviamente, 67108872
está mucho más cerca de 67108871
que 67108864
por lo que la conversión del valor int de 32 bits 67108871
da 67108872
(redondeando al modo par más cercano).
Ahora los números de OP (aún redondeando al par más cercano):
2147483583 = 01111111_11111111_11111111_10111111
= 00000000_1.[1111111_11111111_11111111] 0111111 * 2^30
valores de paréntesis:
parte superior:
00000000_1.[1111111_111111111_11111111] 0111111 * 2^30
+1
= 00000000_10.[0000000_00000000_00000000] * 2^30
= 00000000_1.[0000000_00000000_00000000] * 2^31 = 2147483648
fondo:
00000000_1.[1111111_111111111_11111111] * 2^30 = 2147483520
Tenga en cuenta que incluso la palabra "redondear al par más cercano" solo importa cuando el valor de entrada está a mitad de camino entre los valores del paréntesis. Solo entonces la palabra incluso importa y ''decide'' qué valor de corchete debe seleccionarse. En el caso anterior, incluso no importa y simplemente debemos elegir un valor más cercano, que es 2147483520
El caso del último OP muestra el problema donde incluso la palabra importa. :
2147483584 = 01111111_11111111_11111111_11000000
= 00000000_1.[1111111_11111111_11111111] 1000000 * 2^30
los valores de paréntesis son los mismos que antes:
arriba: 00000000_1.[0000000_00000000_00000000] * 2^31 = 2147483648
abajo: 00000000_1.[1111111_111111111_11111111] * 2^30 = 2147483520
No hay un valor más cercano ahora (2147483648-2147483584 = 64 = 2147483584-2147483520), así que debemos confiar en el valor par y el superior (par) 2147483648
.
Y aquí el problema de OP es que Pascal lo describió brevemente. FPU solo funciona en valores firmados y 2147483648
no puede almacenarse como int firmado ya que su valor máximo es 2147483647, por lo tanto, problemas.
Prueba simple (sin presupuestos de documentación) de que la FPU solo funciona en valores firmados, es decir. trata cada valor como firmado depurando esto:
unsigned int test = (1u << 31);
_asm
{
fild [test]
}
Aunque parece que el valor de prueba debe tratarse como sin signo, se cargará como -231 ya que no hay instrucciones separadas para cargar valores firmados y sin firmar en FPU. Del mismo modo, no encontrará instrucciones que le permitan almacenar valores sin signo de FPU a mem. Todo es solo un patrón de patrón tratado como firmado independientemente de cómo lo haya declarado en su programa.
Fue largo, pero espero que alguien aprenda algo de eso.
Considere el siguiente código, que es un SSCCE de mi problema real:
#include <iostream>
int roundtrip(int x)
{
return int(float(x));
}
int main()
{
int a = 2147483583;
int b = 2147483584;
std::cout << a << " -> " << roundtrip(a) << ''/n'';
std::cout << b << " -> " << roundtrip(b) << ''/n'';
}
La salida en mi computadora (Xubuntu 12.04.3 LTS) es:
2147483583 -> 2147483520
2147483584 -> -2147483648
Observe cómo el número positivo b
termina negativo después de la ida y vuelta. ¿Este comportamiento está bien especificado? Hubiera esperado que el int-to-float round-tripping al menos conservara el signo correctamente ...
Hm, en ideone , la salida es diferente:
2147483583 -> 2147483520
2147483584 -> 2147483647
¿Mientras tanto, el equipo de g ++ solucionó un error, o ambas salidas son perfectamente válidas?
Su programa está invocando un comportamiento indefinido debido a un desbordamiento en la conversión de coma flotante a entero. Lo que ves es solo el síntoma habitual en los procesadores x86.
El valor float
más cercano a 2147483584
es 2 31 exactamente (la conversión de un número entero a un punto flotante generalmente redondea al más cercano, que puede ser hacia arriba, y en este caso está activo. Para ser específico, el comportamiento al convertir de un número entero a flotante el punto está definido por la implementación, la mayoría de las implementaciones definen el redondeo como "de acuerdo con el modo de redondeo FPU", y el modo de redondeo predeterminado de la FPU es redondear al más cercano).
Luego, al convertir del flotante que representa 2 31 a int
, ocurre un desbordamiento. Este desbordamiento es un comportamiento indefinido. Algunos procesadores generan una excepción, otros saturan. La instrucción IA-32 cvttsd2si
típicamente generada por los compiladores siempre devuelve INT_MIN
en caso de desbordamiento, independientemente de si el flotante es positivo o negativo.
No debe confiar en este comportamiento, incluso si sabe que tiene como objetivo un procesador Intel: al dirigirse a x86-64, los compiladores pueden emitir, para la conversión de coma flotante a entero, secuencias de instrucciones que aprovechan el comportamiento indefinido para regresar. resultados distintos a los que podría esperar para el tipo de entero de destino .