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
ftoi
. Pero si estás hablando de SSE, ¿por qué no usar solo la instrucción individual CVTTSD2SI
?
double -> int64
encuentran están dentro del 2^52
rango. Estos son particularmente comunes cuando se realizan convoluciones enteras utilizando FFT de punto flotante.