ElBlo

Enteros × Flotantes

Un truco que hace uso y abuso de los números subnormales.

Allá por el año 2020, un compañero docente me mostró una curiosidad que encontró en uno de sus grupos de trabajo práctico: hacer operaciones de punto flotante con números enteros daba resultados correctos para ciertos números.

No, no hablo de multiplicar enteros por 0 😒.

El siguiente ejemplo en ASM debería dejarlo más claro:

section .text
global _start
_start:
  mov eax, 42
  movd xmm0, eax     ; xmm0 tiene el entero 42.

  mov eax, 10
  cvtsi2ss xmm1, eax ; xmm1 tiene el float 10.0f

  ; Multiplicacion de punto flotante.
  ; Uno de los operandos es entero y el otro punto flotante.
  mulss xmm0, xmm1
  movd eax, xmm0     ; eax == 420 (entero)
  ret

Y un ejemplo en C, aunque sea comportamiento indefinido:

#include<stdio.h>
#include<stdint.h>

union {
    int32_t a;
    float b;
} val;

int main() {
    val.a = 15;
    val.b *= 0.2f;
    printf("val: %d\n", val.a); // imprime 3
    return 0;
}

Esto funciona para todos los enteros menores a $2^{23}$.

Representación Punto Flotante 101

Los 23 bits menos significativos de un número en punto flotante de precisión simple nos sirven para identificar la mantisa, los siguientes 8 bits nos dicen cuál va a ser el exponente y luego tenemos un bit para el signo. Un número positivo con exponente mayor a 0 se calcula como: $2^{exponente - 127}\ (1 + \frac{mantisa}{2^{23}})$.

Representación binaria de 6.25

Números Subnormales

Cuando un número en punto flotante tiene exponente $0$, se dice que es un número subnormal. Se escribe como $2^{-126} \frac{mantisa}{2^{23}}$

Los números enteros menores a $2^{23}$, si los interpretamos como un float, van a ser números positivos subnormales, ya que su exponente será 0. Su mantisa será exactamente igual al número.

Entonces, tenemos nuestro número, por ejemplo $15$, y queremos dividirlo por $3.0$. Si interpretamos nuestro $15$ como punto flotante obtenemos $2^{-126} \frac{15}{2^{23}}$

Así que la cuenta que queremos hacer es:

$$ \frac{2^{-126}\frac{15}{2^{23}}}{3} $$

Didiviendo ${15}$ por $3$ nos queda:

$$ 2^{-126} \frac{3}{2^{23}} $$

Y este es un número que si lo escribimos como punto flotante, tiene el bit de signo en $0$, el exponente en $0$ (es subnormal), y la mantisa vale $3$, haciendo que el número se corresponda con la representación del entero $3$.

Esto vale siempre y cuando el resultado de la operatoria pueda escribirse como un número subnormal.

¿Qué uso le podemos dar a esto? No muchos, esto es más bien una curiosidad, y estamos bastante limitados por el hecho de que tanto el número original como el resultado deben ser menores a $2^{23}$. Para colmo, las unidades de punto flotante de los procesadores suelen tener implementaciones muy lentas cuando trabajan con números subnormales, del orden de los 100 ciclos de procesador por operación1.


  1. https://stackoverflow.com/a/54938328/284906 ↩︎

© Marco Vanotti 2024

Powered by Hugo & new.css.