1/√ x, mais genre… vraiment vite

Marc-André Désautels

Quake III Arena

  • Quake III Arena est un jeu vidéo de tir à la première personne développé par id Software et publié par Activision sur PC le 2 décembre 1999.
  • Quake III se déroule dans un univers de science-fiction dans lequel le joueur incarne un combattant participant à un tournoi de combats à mort.
  • Le jeu se focalise sur l’aspect multijoueur.

Quake III Arena

  • Le code source de Quake III a été diffusé après la QuakeCon 2005.
  • On découvre bientôt une fonction étrange: Q_rsqrt.
  • Cette fonction était déjà apparue sur Usenet et sur d’autres forums dès 2002/2003.

Q_rsqrt

L’inverse de la racine carrée, \(\frac{1}{\sqrt{x}}\).

float Q_rsqrt( float number )
{
    long i;
    float x2, y;
    const float threehalfs = 1.5F;

    x2 = number * 0.5F;
    y = number;
    i = * ( long * ) &y; // evil floating point bit level hacking
    i = 0x5f3759df - ( i >> 1 ); // what the fuck?
    y = * ( float * ) &i;
    y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
//  y = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed

    return y;
}

Motivation

  • Les inverses des racines carrées d’un nombre à virgule flottante sont utilisées pour calculer un vecteur normalisé.
  • En synthèse d’image 3D, ces vecteurs normalisés sont utilisés pour déterminer l’éclairage et l’ombrage.
  • Des millions de ces calculs sont ainsi nécessaires chaque seconde.

Normales à la surface

Motivation

  • Soit un vecteur \(\vec{v}=(v_1,v_2,v_3)\).
  • Sa norme est donnée par \(||\vec{v}||=\sqrt{v_1^2+v_2^2+v_3^2}\).
  • Le vecteur unitaire est donc \(\frac{\vec{v}}{||\vec{v}||}=\frac{\vec{v}}{\sqrt{v_1^2+v_2^2+v_3^2}}\)

Q_sqrt

float Q_rsqrt( float number )
{
    long i;
    float x2, y;
    const float threehalfs = 1.5F;

    x2 = number * 0.5F;
    y = number;
    i = * ( long * ) &y; // evil floating point bit level hacking
    i = 0x5f3759df - ( i >> 1 ); // what the fuck?
    y = * ( float * ) &i;
    y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
//  y = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed

    return y;
}

Python

def Q_rsqrt(number):

    threehalfs = 1.5
    
    x2 = number * 0.5
    y = np.float32(number)                    
    i = y.view(np.int32)                          # evil floating point bit level hacking
    i = np.int32(0x5f3759df) - np.int32(i >> 1)   # what the fuck?
    y = i.view(np.float32)
    y = y * (threehalfs - (x2 * y * y))           # 1st iteration
    # y = y * (threehalfs - (x2 * y * y))         # 2nd iteration, this can be removed
    
    return float(y)

Est-ce que cette approximation est bonne?

Est-ce que cette approximation est bonne?

Est-ce que cette approximation est bonne?

Est-ce que cette approximation est bonne?

La représentation des nombres

Système de numération

Un système de numération permet de compter des objets et de les représenter par des nombres. Un système de numération positionnel possède trois éléments :

  • Base \(b\) (un entier supérieur à 1)

  • Symboles (digits) : 0, 1, 2, …, \(b-1\)

  • Poids des symboles selon la position et la base, où poids=baseposition

Représentation polynomiale

Le système positionnel utilise la représentation polynomiale. Celle-ci est donnée par:

\[ \begin{aligned} (a_na_{n-1}\ldots a_1a_0,a_{-1}a_{-2}\ldots a_{-m})_b &= \sum_{k=-m}^n a_k b^k \end{aligned} \]

\(b\) est la base et les \(a_i\) sont des coefficients (les symboles de votre système de numération).

Représentation binaire

  • Base = 2
  • Symboles ordonnés qu’on nomme les chiffres : 0, 1.
  • Le poids des symboles est donné par 2position

Par exemple:

\[ \begin{align} (1 \ 0100\ 1111)_2 &= 1\cdot 2^8 + 1\cdot 2^6 + 1\cdot 2^3 + 1\cdot 2^2 + \\ & \qquad + 1\cdot 2^1 + 1\cdot 2^0 \\ &= (335)_{10} \end{align} \]

Représentation binaire

  • On précède le nombre par 0b si on veut identifier un nombre sous forme binaire en Python.

Par exemple:

a = 101001111
b = 0b101001111
print(a, b)
101001111 335

Représentation hexadécimale

  • Base = 16
  • Symboles ordonnés qu’on nomme les chiffres : 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F.
  • Le poids des symboles est donné par 16position

Par exemple:

\[ \begin{align} (14F)_{16} &= 1\cdot 16^2 + 4\cdot 16^1 + 15\cdot 16^0 \\ &= (335)_{10} \end{align} \]

Représentation hexadécimale

  • On précède le nombre par 0x si on veut identifier un nombre sous forme hexadécimale en Python.

Par exemple:

a = 335
b = 0x14f
print(a, b)
335 335

Les trains de bits

  • Les ordinateurs utilisent des bits pour emmagasiner de l’information.

  • Un bit peut prendre la valeur 0 ou la valeur 1.

  • L’information est emmagasinée dans des trains de bits (\(T_n\)) de longueur \(n\) (une succession de \(n\) bits).

  • \(T_4 = 0110\)

Les entiers non signés

Les entiers non signés

Entiers non signés

La représentation binaire non signée sur \(n\) bits d’un entier \(x\) est le train de bits correspondant à l’écriture de \(x\) en base 2.

Par exemple, en utilisant 8 bits:

Les entiers signés

Entiers signés

Entiers signés

La représentation binaire signée sur \(n\) bits d’un entier est le train de bit où le premier bit de gauche représente le signe (positif si le bit est 0 et négatif si le bit est 1), et où les \(n-1\) bits restants représentent l’écriture de \(|x|\) en base 2.

Par exemple sur huit bits:

Entiers signés

Entiers signés

La représentation binaire signée sur \(n\) bits d’un entier est le train de bit où le premier bit de gauche représente le signe (positif si le bit est 0 et négatif si le bit est 1), et où les \(n-1\) bits restants représentent l’écriture de \(|x|\) en base 2.

Par exemple sur huit bits:

Les entiers signés

  • En pratique, les nombres négatifs sont représentés un peu différemment, à l’aide du complément à deux.
  • Nous voulons calculer l’inverse de la racine carrée d’un nombre.
  • Nous supposons que le nombre est positif.
  • Le bit à gauche est toujours 0.

Le format entier long

  • 32 bits au total
  • 1 bit pour le signe
  • 31 bits pour la mantisse
  • Le maximum représentable est \(2^{31}-1=2\ 147\ 483\ 647\)

Un exemple “célèbre”

  • On représente l’entier 1 346 269 en utilisant un entier long.

En Python.

0b000000000101001000101011011101
1346269
0x148add
1346269

C’est le 31ème nombre de la suite de Fibonacci.

Virgule flottante

Représentation normalisée

  • En décimal, nous avons:

\[ +13,254 = \underbrace{+}_{\text{signe}} 0,\underbrace{13254}_{\text{mantisse}} \times 10^{\overbrace{{2}}^{\text{exposant}}} \]

Représentation normalisée

  • Par contre, en binaire, nous avons:

\[ +10,101 = \underbrace{+}_{\text{signe}} \overbrace{1}^{\text{toujours}\ 1},\underbrace{0101}_{\text{mantisse}} \times 2^{\overbrace{{1}}^{\text{exposant}}} \]

  • Le premier 1 est implicite et n’a pas besoin d’être stocké.

La norme IEEE 754

La norme IEEE 754

  • 1 bit pour le signe \(S_x\)
  • \(e\) bits pour l’exposant \(E_x\) biaisé (pour trouver l’exposant réel, il faut soustraire une valeur (le biais) à celle stockée.)
  • \(m\) bits pour la mantisse \(M_x\) (un entier entre 0 et \(2^m-1\))

Un nombre flottant normalisé a une valeur \(N_x\) donnée par la formule suivante: \[ N_x = (-1)^{S_x} \times 2^{E_x-\text{BIAIS}} \times \left(1+\frac{M_x}{2^m}\right) \]

Le format simple précision

  • 32 bits au total
  • 1 bit pour le signe \(S_x\)
  • 8 bits pour l’exposant \(E_x\) biaisé (biais de 127)
  • 23 bits pour la mantisse \(M_x\) (de \(0\) à \(2^{23}-1\))

Le format simple précision

  • 32 bits au total
  • 1 bit pour le signe \(S_x\)
  • 8 bits pour l’exposant \(E_x\) biaisé (biais de 127)
  • 23 bits pour la mantisse \(M_x\) (de \(0\) à \(2^{23}-1\))

\[ N_x = (-1)^{S_x} \times 2^{E_x-127} \times \left(1+\frac{M_x}{2^{23}}\right) \]

Un exemple “célèbre”

  • On représente \(\pi\) en utilisant le format simple précision.

\[ \pi \approx (-1)^0 \cdot 2^{128-127} \cdot \left(1+\frac{4\ 788\ 187}{2^{23}} \right) \]

\[ \pi \approx 1 \cdot 2^1 \cdot 1,570796371 \]

La représentation précédente donne \(\pi \approx 3,141592741\).

Trains de bits

Trains de bits

  • Un entier long sur 32 bits.

  • Un flottant simple précision sur 32 bits.

Trains de bits

  • Représente l’entier 1078530011.
  • Représenté par 0x40490fdb.

  • Représente le nombre décimal 3,1415927.
  • Aussi représenté par 0x40490fdb.

Trains de bits

  • Posons \(L=2^{23}\).
  • Lorsqu’on interprète le nombre en virgule flottante comme un entier, nous avons \(R_x = E_x \cdot L + M_x\).

Trains de bits

  • Posons \(L=2^{23}\).
  • Lorsqu’on interprète le nombre en virgule flottante comme un entier, nous avons \(R_x = E_x \cdot L + M_x\).

Entier long \(\leftrightarrow\) Simple précision

  • Les lignes suivantes du code interprètent un nombre à virgule flottante en entier et ensuite le réinterprète comme un nombre à virgule flottante.
def Q_rsqrt(number):

    threehalfs = 1.5
    
    x2 = number * 0.5
    y = np.float32(number)                    
    i = y.view(np.int32)                          # evil floating point bit level hacking
    i = np.int32(0x5f3759df) - np.int32(i >> 1)   # what the fuck?
    y = i.view(np.float32)
    y = y * (threehalfs - (x2 * y * y))           # 1st iteration
    # y = y * (threehalfs - (x2 * y * y))           # 2nd iteration, this can be removed
    
    return float(y)

Opérations bit à bit

Décalages de bit

  • En logique, une opération bit à bit est un calcul manipulant les données directement au niveau des bits, selon une arithmétique booléenne.
  • Les opérations qui nous intéressent particulièrement sont les décalages de bits.

Décalage à gauche (<<)

  • En base décimale un décalage à gauche représente une multiplication par 10.
  • En base binaire, un décalage à gauche correspond à une multiplication par 2.

Décalage à droite (>>)

  • En base décimale un décalage à droite représente une division par 10 (on arrondit à l’entier inférieur).
  • En base binaire, un décalage à droite correspond à une division par 2 (on arrondit à l’entier inférieur).

Décalage à droite (>>)

  • En Python:
0b100001
33
0b100001 >> 1
16
  • ou alors
0b10010110
150
0b10010110 >> 1
75

Décalage à droite (>>)

  • On utilise le décalage à droite dans le code à la ligne suivante:
def Q_rsqrt(number):

    threehalfs = 1.5
    
    x2 = number * 0.5
    y = np.float32(number)                    
    i = y.view(np.int32)                          # evil floating point bit level hacking
    i = np.int32(0x5f3759df) - np.int32(i >> 1)   # what the fuck?
    y = i.view(np.float32)
    y = y * (threehalfs - (x2 * y * y))           # 1st iteration
    # y = y * (threehalfs - (x2 * y * y))           # 2nd iteration, this can be removed
    
    return float(y)

La méthode de Newton

La méthode de Newton

  • La méthode de Newton est un algorithme pour trouver numériquement une approximation précise d’un zéro d’une fonction \(f(x)\).
  • Si \(f(x)=0\) pour un certain \(x\), alors si on choisit \(x_0\) suffisament proche de \(x\), la suite ci-dessous converge vers la valeur de \(x\):

\[ x_{n+1} = x_n - \frac{f(x_n)}{f'(x_n)} \]

La méthode de Newton

La méthode de Newton animée

La méthode de Newton pour \(\frac{1}{\sqrt{x}}\)

  • Considérons la fonction \(f(y)=\frac{1}{y^2}-x\), qui possède comme zéro \(y=\frac{1}{\sqrt{x}}\).
  • En appliquant la méthode de Newton, nous obtenons la suite:

\[ y_{n+1} = \frac{y_n(3-xy_n^2)}{2} = y_n \left( \frac{3}{2}-\frac{x}{2}y_n^2 \right) \]

qui est exactement la ligne

y = y * (threehalfs - (x2 * y * y))           # 1st iteration

La méthode de Newton pour \(\frac{1}{\sqrt{x}}\)

  • On utilise la méthode de Newton dans le code à la ligne suivante:
def Q_rsqrt(number):

    threehalfs = 1.5
    
    x2 = number * 0.5
    y = np.float32(number)                    
    i = y.view(np.int32)                          # evil floating point bit level hacking
    i = np.int32(0x5f3759df) - np.int32(i >> 1)   # what the fuck?
    y = i.view(np.float32)
    y = y * (threehalfs - (x2 * y * y))           # 1st iteration
    # y = y * (threehalfs - (x2 * y * y))           # 2nd iteration, this can be removed
    
    return float(y)

La méthode de Newton pour \(\frac{1}{\sqrt{x}}\)

  • La ligne 11 nous indique qu’une deuxième itération n’est pas nécessaire.
def Q_rsqrt(number):

    threehalfs = 1.5
    
    x2 = number * 0.5
    y = np.float32(number)                    
    i = y.view(np.int32)                          # evil floating point bit level hacking
    i = np.int32(0x5f3759df) - np.int32(i >> 1)   # what the fuck?
    y = i.view(np.float32)
    y = y * (threehalfs - (x2 * y * y))           # 1st iteration
    # y = y * (threehalfs - (x2 * y * y))           # 2nd iteration, this can be removed
    
    return float(y)

Q_rsqrt

Q_rsqrt

def Q_rsqrt(number):

    threehalfs = 1.5
    
    x2 = number * 0.5
    y = np.float32(number)                    
    i = y.view(np.int32)                          # evil floating point bit level hacking
    i = np.int32(0x5f3759df) - np.int32(i >> 1)   # what the fuck?
    y = i.view(np.float32)
    y = y * (threehalfs - (x2 * y * y))           # 1st iteration
    # y = y * (threehalfs - (x2 * y * y))         # 2nd iteration, this can be removed
    
    return float(y)
  • On assigne la valeur \(\frac{3}{2}\) à la variable threehalfs.

Q_rsqrt

def Q_rsqrt(number):

    threehalfs = 1.5
    
    x2 = number * 0.5
    y = np.float32(number)                    
    i = y.view(np.int32)                          # evil floating point bit level hacking
    i = np.int32(0x5f3759df) - np.int32(i >> 1)   # what the fuck?
    y = i.view(np.float32)
    y = y * (threehalfs - (x2 * y * y))           # 1st iteration
    # y = y * (threehalfs - (x2 * y * y))         # 2nd iteration, this can be removed
    
    return float(y)
  • On multiplie le nombre dont on veut trouver l’inverse de la racine carrée par \(0,5\).

Q_rsqrt

def Q_rsqrt(number):

    threehalfs = 1.5
    
    x2 = number * 0.5
    y = np.float32(number)                    
    i = y.view(np.int32)                          # evil floating point bit level hacking
    i = np.int32(0x5f3759df) - np.int32(i >> 1)   # what the fuck?
    y = i.view(np.float32)
    y = y * (threehalfs - (x2 * y * y))           # 1st iteration
    # y = y * (threehalfs - (x2 * y * y))         # 2nd iteration, this can be removed
    
    return float(y)
  • On mémorise le nombre dans la variable y qui est un nombre à virgule flottante simple précision.

Q_rsqrt

def Q_rsqrt(number):

    threehalfs = 1.5
    
    x2 = number * 0.5
    y = np.float32(number)                    
    i = y.view(np.int32)                          # evil floating point bit level hacking
    i = np.int32(0x5f3759df) - np.int32(i >> 1)   # what the fuck?
    y = i.view(np.float32)
    y = y * (threehalfs - (x2 * y * y))           # 1st iteration
    # y = y * (threehalfs - (x2 * y * y))         # 2nd iteration, this can be removed
    
    return float(y)
  • On interprète la valeur mémorisée de \(y\) comme un entier long, plutôt que comme un nombre à virgule flottante simple précision.

Q_rsqrt

def Q_rsqrt(number):

    threehalfs = 1.5
    
    x2 = number * 0.5
    y = np.float32(number)                    
    i = y.view(np.int32)                          # evil floating point bit level hacking
    i = np.int32(0x5f3759df) - np.int32(i >> 1)   # what the fuck?
    y = i.view(np.float32)
    y = y * (threehalfs - (x2 * y * y))           # 1st iteration
    # y = y * (threehalfs - (x2 * y * y))         # 2nd iteration, this can be removed
    
    return float(y)
  • On effectue une soustraction et un décalage à droite (qui divise la variable \(i\) par deux).

Q_rsqrt

def Q_rsqrt(number):

    threehalfs = 1.5
    
    x2 = number * 0.5
    y = np.float32(number)                    
    i = y.view(np.int32)                          # evil floating point bit level hacking
    i = np.int32(0x5f3759df) - np.int32(i >> 1)   # what the fuck?
    y = i.view(np.float32)
    y = y * (threehalfs - (x2 * y * y))           # 1st iteration
    # y = y * (threehalfs - (x2 * y * y))         # 2nd iteration, this can be removed
    
    return float(y)
  • On réinterprète la valeur mémorisée de \(i\) comme un nombre à virgule flottante simple précision, plutôt que comme un entier long.

Q_rsqrt

def Q_rsqrt(number):

    threehalfs = 1.5
    
    x2 = number * 0.5
    y = np.float32(number)                    
    i = y.view(np.int32)                          # evil floating point bit level hacking
    i = np.int32(0x5f3759df) - np.int32(i >> 1)   # what the fuck?
    y = i.view(np.float32)
    y = y * (threehalfs - (x2 * y * y))           # 1st iteration
    # y = y * (threehalfs - (x2 * y * y))         # 2nd iteration, this can be removed
    
    return float(y)
  • On effectue une itération de la méthode de Newton.

Q_rsqrt

def Q_rsqrt(number):

    threehalfs = 1.5
    
    x2 = number * 0.5
    y = np.float32(number)                    
    i = y.view(np.int32)                          # evil floating point bit level hacking
    i = np.int32(0x5f3759df) - np.int32(i >> 1)   # what the fuck?
    y = i.view(np.float32)
    y = y * (threehalfs - (x2 * y * y))           # 1st iteration
    # y = y * (threehalfs - (x2 * y * y))         # 2nd iteration, this can be removed
    
    return float(y)
  • La deuxième itération de la méthode de Newton n’est pas nécessaire!

C’est quoi le truc?

evil floating point bit level hacking

  • À quoi vous fait penser ce graphique??

evil floating point bit level hacking

Interpréter un nombre à virgule flottante positif \(x\) comme un entier donne une approximation de son logarithme en base \(2\).

\[ N_x = 2^{E_x-B} \cdot \left(1+\frac{M_x}{L}\right) \]\(B=127\) et \(L=2^{23}\).

evil floating point bit level hacking

\[ N_x = 2^{E_x-B} \cdot \left(1+\frac{M_x}{L}\right) \]

\[ \log_2 (N_x) = E_x-B + \log_2 \left(1+\frac{M_x}{L}\right) \]

Puisque \(0\leq\frac{M_x}{L}<1\) alors:

\[ \log_2 \left(1+\frac{M_x}{L}\right) \approx \frac{M_x}{L} \]

evil floating point bit level hacking

\[ N_x = 2^{E_x-B} \cdot \left(1+\frac{M_x}{L}\right) \]

\[ \log_2 (N_x) = E_x-B + \log_2 \left(1+\frac{M_x}{L}\right) \]

Puisque \(0\leq\frac{M_x}{L}<1\) alors:

\[ \log_2 \left(1+\frac{M_x}{L}\right) \approx \frac{M_x}{L} + \sigma \]

evil floating point bit level hacking

\[ \log_2 \left(1+\frac{M_x}{L}\right) \approx \frac{M_x}{L} + \sigma \]

  • \(\sigma\) est un paramètre qui permet d’ajuster l’approximation.
  • Par exemple, si \(\sigma=0\), l’approximation est exacte aux extrémités de l’intervalle.

evil floating point bit level hacking

evil floating point bit level hacking

\[ \log_2 \left(1+\frac{M_x}{L}\right) \approx \frac{M_x}{L} + \sigma \]

  • \(\sigma\) est un paramètre qui permet d’ajuster l’approximation.
  • Si nous choisissons \(\sigma=\frac{1}{2}-\frac{1+\ln(\ln(2))}{2\ln(2)}\approx 0,0430357\), ça permet de minimiser l’erreur uniforme.

evil floating point bit level hacking

evil floating point bit level hacking

\[ \log_2 \left(1+\frac{M_x}{L}\right) \approx \frac{M_x}{L} + \sigma \]

  • \(\sigma\) est un paramètre qui permet d’ajuster l’approximation.
  • Par contre, ce n’est pas la valeur choisie car elle ne tient pas compte des autres étapes de l’algorithme.
  • La valeur choisie est \(\sigma\approx 0,04504656791687012\).

evil floating point bit level hacking

evil floating point bit level hacking

i = np.int32(0x5f3759df) - np.int32(i >> 1)   # what the fuck?

evil floating point bit level hacking

\[ \log_2 (N_x) = E_x-B + \log_2 \left(1+\frac{M_x}{L}\right) \]

\[ \log_2 (N_x) \approx E_x-B + \frac{M_x}{L} + \sigma \]

\[ L\cdot \log_2 (N_x) \approx E_x\cdot L-L\cdot B + M_x + L\cdot\sigma \]

\[ E_x\cdot L + M_x \approx L\cdot \log_2 (N_x) + L\cdot B - L\cdot \sigma \]

\[ E_x\cdot L + M_x \approx L\cdot \log_2 (N_x) +L(B-\sigma) \]

evil floating point bit level hacking

Lorsqu’on interprète un nombre en virgule flottante comme un entier, nous avons: \(R_x=E_x \cdot L + M_x\).

\[ R_x \approx L\log_2(N_x) + L(B-\sigma) \]

\[ \log_2(N_x) = \frac{R_x}{L} - (B-\sigma) \]

evil floating point bit level hacking

  • Si \(y=\frac{1}{\sqrt{x}}\) alors \(\log_2(y) = -\frac{1}{2}\log_2(x)\).
\[\begin{aligned} \log_2(y) &= -\frac{1}{2}\log_2(x) \\ \frac{R_y}{L} - (B-\sigma) &= -\frac{1}{2}\left(\frac{R_x}{L} - (B-\sigma) \right) \\ R_y &= \frac{3}{2}L(B-\sigma) - \frac{1}{2}R_x \end{aligned}\]
i = np.int32(0x5f3759df) - np.int32(i >> 1)   # what the fuck?

Magic number

\[ R_y = \frac{3}{2}L(B-\sigma) - \frac{1}{2}R_x \]

i = np.int32(0x5f3759df) - np.int32(i >> 1)   # what the fuck?

Le premier terme de droite de l’équation est le nombre magique.

\[ \frac{3}{2}L(B-\sigma) = 0x5F3759DF \]

La valeur de \(\sigma\) est donc \(\sigma \approx 0,0450466\).

Un exemple

Nous allons calculer \(\frac{1}{\sqrt{\pi}}\)

def Q_rsqrt(number):

    threehalfs = 1.5
    
    x2 = number * 0.5
    y = np.float32(number)                    
    i = y.view(np.int32)                          # evil floating point bit level hacking
    i = np.int32(0x5f3759df) - np.int32(i >> 1)   # what the fuck?
    y = i.view(np.float32)
    y = y * (threehalfs - (x2 * y * y))           # 1st iteration
    # y = y * (threehalfs - (x2 * y * y))           # 2nd iteration, this can be removed
    
    return float(y)

def Q_rsqrt(number):

    threehalfs = 1.5
    
    x2 = number * 0.5
    y = np.float32(number)                    
    i = y.view(np.int32)                          # evil floating point bit level hacking
    i = np.int32(0x5f3759df) - np.int32(i >> 1)   # what the fuck?
    y = i.view(np.float32)
    y = y * (threehalfs - (x2 * y * y))           # 1st iteration
    # y = y * (threehalfs - (x2 * y * y))           # 2nd iteration, this can be removed
    
    return float(y)

On assigne à la variable number, la valeur de \(\pi\).

def Q_rsqrt(number):

    threehalfs = 1.5
    
    x2 = number * 0.5
    y = np.float32(number)                    
    i = y.view(np.int32)                          # evil floating point bit level hacking
    i = np.int32(0x5f3759df) - np.int32(i >> 1)   # what the fuck?
    y = i.view(np.float32)
    y = y * (threehalfs - (x2 * y * y))           # 1st iteration
    # y = y * (threehalfs - (x2 * y * y))           # 2nd iteration, this can be removed
    
    return float(y)

La variable y est un nombre à virgule flottante simple précision.

def Q_rsqrt(number):

    threehalfs = 1.5
    
    x2 = number * 0.5
    y = np.float32(number)                    
    i = y.view(np.int32)                          # evil floating point bit level hacking
    i = np.int32(0x5f3759df) - np.int32(i >> 1)   # what the fuck?
    y = i.view(np.float32)
    y = y * (threehalfs - (x2 * y * y))           # 1st iteration
    # y = y * (threehalfs - (x2 * y * y))           # 2nd iteration, this can be removed
    
    return float(y)

On interprète la valeur mémorisée y comme un entier long, plutôt que comme un nombre à virgule flottante simple précision,

def Q_rsqrt(number):

    threehalfs = 1.5
    
    x2 = number * 0.5
    y = np.float32(number)                    
    i = y.view(np.int32)                          # evil floating point bit level hacking
    i = np.int32(0x5f3759df) - np.int32(i >> 1)   # what the fuck?
    y = i.view(np.float32)
    y = y * (threehalfs - (x2 * y * y))           # 1st iteration
    # y = y * (threehalfs - (x2 * y * y))           # 2nd iteration, this can be removed
    
    return float(y)

On effectue le décalage à droite de la variable i

def Q_rsqrt(number):

    threehalfs = 1.5
    
    x2 = number * 0.5
    y = np.float32(number)                    
    i = y.view(np.int32)                          # evil floating point bit level hacking
    i = np.int32(0x5f3759df) - np.int32(i >> 1)   # what the fuck?
    y = i.view(np.float32)
    y = y * (threehalfs - (x2 * y * y))           # 1st iteration
    # y = y * (threehalfs - (x2 * y * y))           # 2nd iteration, this can be removed
    
    return float(y)

La variable i est obtenue en soustrayant le décalage à droite précédent et le magic number \(0x5F3759DF\).

def Q_rsqrt(number):

    threehalfs = 1.5
    
    x2 = number * 0.5
    y = np.float32(number)                    
    i = y.view(np.int32)                          # evil floating point bit level hacking
    i = np.int32(0x5f3759df) - np.int32(i >> 1)   # what the fuck?
    y = i.view(np.float32)
    y = y * (threehalfs - (x2 * y * y))           # 1st iteration
    # y = y * (threehalfs - (x2 * y * y))           # 2nd iteration, this can be removed
    
    return float(y)

On réinterprète la variable i comme un nombre à virgule flottante simple précision.

def Q_rsqrt(number):

    threehalfs = 1.5
    
    x2 = number * 0.5
    y = np.float32(number)                    
    i = y.view(np.int32)                          # evil floating point bit level hacking
    i = np.int32(0x5f3759df) - np.int32(i >> 1)   # what the fuck?
    y = i.view(np.float32)
    y = y * (threehalfs - (x2 * y * y))           # 1st iteration
    # y = y * (threehalfs - (x2 * y * y))           # 2nd iteration, this can be removed
    
    return float(y)

On effectue une itération de la méthode Newton.

def Q_rsqrt(number):

    threehalfs = 1.5
    
    x2 = number * 0.5
    y = np.float32(number)                    
    i = y.view(np.int32)                          # evil floating point bit level hacking
    i = np.int32(0x5f3759df) - np.int32(i >> 1)   # what the fuck?
    y = i.view(np.float32)
    y = y * (threehalfs - (x2 * y * y))           # 1st iteration
    # y = y * (threehalfs - (x2 * y * y))           # 2nd iteration, this can be removed
    
    return float(y)

La seconde itération de la méthode Newton n’est pas nécessaire.

La valeur réelle est \(\frac{1}{\sqrt{\pi}}=0,56418958\ldots\)

Devrait-on utiliser cette fonction?

Non…

Pourquoi ne pas utiliser ce code?

  • Les ajouts des fabricants de matériel ont rendu cet algorithme en grande partie obsolète.
  • Par exemple, sur l’architecture x86, Intel a introduit l’instruction SSE rsqrtss en 1999. Cette instruction prend 0,85 ns par nombre flottant, contre 3,54 ns pour l’algorithme rapide d’inverse de racine carrée, tout en ayant une erreur moindre.

Des questions?