Al leer el código fuente de Lua , noté que Lua usa a macro
para redondear double
a 32 bits int
. Extraje el macro
, y se ve así:
union i_cast {double d; int i[2]};
#define double2int(i, d, t) \
{volatile union i_cast u; u.d = (d) + 6755399441055744.0; \
(i) = (t)u.i[ENDIANLOC];}
Aquí ENDIANLOC
se define como endianness , 0
para little endian, 1
para big endian. Lua maneja con cuidado el endianness. t
representa el tipo entero, como int
o unsigned int
.
Investigué un poco y hay un formato más simple macro
que usa el mismo pensamiento:
#define double2int(i, d) \
{double t = ((d) + 6755399441055744.0); i = *((int *)(&t));}
O en un estilo C ++:
inline int double2int(double d)
{
d += 6755399441055744.0;
return reinterpret_cast<int&>(d);
}
Este truco puede funcionar en cualquier máquina que use IEEE 754 (lo que significa que prácticamente todas las máquinas actuales). Funciona tanto para números positivos como negativos, y el redondeo sigue la Regla del banquero . (Esto no es sorprendente, ya que sigue a IEEE 754.)
Escribí un pequeño programa para probarlo:
int main()
{
double d = -12345678.9;
int i;
double2int(i, d)
printf("%d\n", i);
return 0;
}
Y genera -12345679, como se esperaba.
Me gustaría entrar en detalles sobre cómo funciona este truco macro
. El número mágico 6755399441055744.0
es en realidad 2^51 + 2^52
, o 1.5 * 2^52
, y 1.5
en binario se puede representar como 1.1
. Cuando se agrega cualquier número entero de 32 bits a este número mágico, bueno, estoy perdido desde aquí. ¿Cómo funciona este truco?
PD: Esto está en el código fuente de Lua, Llimits.h .
ACTUALIZACIÓN :
- Como señala @Mysticial, este método no se limita a 32 bits
int
, también se puede ampliar a 64 bitsint
siempre que el número esté en el rango de 2 ^ 52. (macro
Necesita algunas modificaciones). - Algunos materiales dicen que este método no se puede usar en Direct3D .
Al trabajar con el ensamblador de Microsoft para x86, hay una
macro
escritura aún más rápidaassembly
(esto también se extrae de la fuente Lua):#define double2int(i,n) __asm {__asm fld n __asm fistp i}
Hay un número mágico similar para un número de precisión simple:
1.5 * 2 ^23
fuente
ftoi
. Pero si estás hablando de SSE, ¿por qué no usar solo la instrucción individualCVTTSD2SI
?double -> int64
encuentran están dentro del2^52
rango. Estos son particularmente comunes cuando se realizan convoluciones enteras utilizando FFT de punto flotante.Respuestas:
A
double
se representa así:y puede verse como dos enteros de 32 bits; ahora, el
int
tomado en todas las versiones de su código (suponiendo que sea de 32 bitsint
) es el que está a la derecha en la figura, por lo que lo que está haciendo al final es tomar los 32 bits más bajos de mantisa.Ahora, al número mágico; como usted dijo correctamente, 6755399441055744 es 2 ^ 51 + 2 ^ 52; agregar tal número obliga
double
a entrar en el "rango dulce" entre 2 ^ 52 y 2 ^ 53, que, como explica Wikipedia aquí , tiene una propiedad interesante:Esto se deduce del hecho de que la mantisa tiene 52 bits de ancho.
El otro hecho interesante sobre la adición de 2 51 +2 52 es que afecta a la mantisa solo en los dos bits más altos, que de todos modos se descartan, ya que solo estamos tomando sus 32 bits más bajos.
Por último, pero no menos importante: el signo.
El punto flotante IEEE 754 usa una representación de magnitud y signo, mientras que los enteros en máquinas "normales" usan aritmética de complemento a 2; ¿Cómo se maneja esto aquí?
Hablamos solo de enteros positivos; ahora supongamos que estamos tratando con un número negativo en el rango representable por un bit de 32 bits
int
, por lo que es menor (en valor absoluto) que (-2 ^ 31 + 1); llámalo-a
. Tal número obviamente se hace positivo al sumar el número mágico, y el valor resultante es 2 52 +2 51 + (- a).Ahora, ¿qué obtenemos si interpretamos la mantisa en la representación del complemento a 2? Debe ser el resultado de la suma del complemento a 2 de (2 52 +2 51 ) y (-a). Nuevamente, el primer término afecta solo a los dos bits superiores, lo que queda en los bits 0 ~ 50 es la representación del complemento a 2 de (-a) (nuevamente, menos los dos bits superiores).
Dado que la reducción del número de complemento de 2 a un ancho menor se realiza cortando los bits adicionales a la izquierda, tomar los 32 bits inferiores nos da correctamente (-a) en 32 bits, aritmética de complemento de 2.
fuente
int64_t
, pueden hacerlo desplazando la mantisa a la izquierda y luego a la derecha 13 bits. Esto borrará el exponente y los dos bits del número 'mágico', pero mantendrá y propagará el signo a todo el entero con signo de 64 bits.union { double d; int64_t l; } magic; magic.d = input + 6755399441055744.0; magic.l <<= 13; magic.l >>= 13;