Al leer el código fuente de Lua , noté que Lua usa a macropara redondear doublea 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í ENDIANLOCse define como endianness , 0para little endian, 1para big endian. Lua maneja con cuidado el endianness. trepresenta el tipo entero, como into unsigned int.
Investigué un poco y hay un formato más simple macroque 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.0es en realidad 2^51 + 2^52, o 1.5 * 2^52, y 1.5en 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 bitsintsiempre que el número esté en el rango de 2 ^ 52. (macroNecesita 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
macroescritura 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 -> int64encuentran están dentro del2^52rango. Estos son particularmente comunes cuando se realizan convoluciones enteras utilizando FFT de punto flotante.Respuestas:
A
doublese representa así:y puede verse como dos enteros de 32 bits; ahora, el
inttomado 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
doublea 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;