Greg Hewgill et IllidanS4 ont donné un lien avec une excellente explication mathématique. Je vais essayer de résumer ici pour ceux qui ne veulent pas entrer trop dans les détails.
Toute fonction mathématique, à quelques exceptions près, peut être représentée par une somme de polynômes :
y = f(x)
peut être exactement transformé en :
y = a0 + a1*x + a2*(x^2) + a3*(x^3) + a4*(x^4) + ...
Où a0, a1, a2,... sont des constants. Le problème est que pour de nombreuses fonctions, comme la racine carrée, pour une valeur exacte cette somme a un nombre infini de membres, elle ne se termine pas à un certain x^n. Mais, si nous nous arrêtons à un certain x^n, nous aurions quand même un résultat jusqu'à une certaine précision.
Donc, si nous avons :
y = 1/sqrt(x)
Dans ce cas particulier, ils ont décidé de se débarrasser de tous les membres polynomiaux au-dessus de deux, probablement à cause de la vitesse de calcul :
y = a0 + a1*x + [...discarded...]
Et la tâche est maintenant de calculer a0 et a1 afin que y ait la moins de différence possible par rapport à la valeur exacte. Ils ont calculé que les valeurs les plus appropriées sont :
a0 = 0x5f375a86
a1 = -0.5
Donc quand vous mettez cela dans l'équation vous obtenez :
y = 0x5f375a86 - 0.5*x
Ce qui est la même que la ligne que vous voyez dans le code :
i = 0x5f375a86 - (i >> 1);
Edit : en fait ici y = 0x5f375a86 - 0.5*x
n'est pas la même que i = 0x5f375a86 - (i >> 1);
car le décalage du flottant en tant qu'entier divise non seulement par deux mais divise aussi l'exposant par deux et provoque d'autres artefacts, mais cela revient toujours à calculer certains coefficients a0, a1, a2... .
À ce stade, ils ont découvert que la précision de ce résultat n'était pas suffisante pour l'objectif. Ils ont donc effectué une seule étape de l'itération de Newton pour améliorer la précision du résultat :
x = x * (1.5f - xhalf * x * x)
ils auraient pu faire d'autres itérations dans une boucle, chacune améliorant le résultat, jusqu'à ce que la précision requise soit atteinte. C'est exactement ainsi que cela fonctionne dans le CPU/FPU ! Mais il semblerait qu'une seule itération ait été suffisante, ce qui était également une bénédiction pour la vitesse. Le CPU/FPU effectue autant d'itérations que nécessaire pour atteindre la précision du nombre à virgule flottante dans lequel le résultat est stocké et il dispose d'un algorithme plus général qui fonctionne pour tous les cas.
En bref, ce qu'ils ont fait c'est :
Utiliser (presque) le même algorithme que le CPU/FPU, exploiter l'amélioration des conditions initiales pour le cas spécial de 1/sqrt(x) et ne pas calculer jusqu'à la précision à laquelle le CPU/FPU ira mais s'arrêter avant, ce qui permet de gagner en vitesse de calcul.
7 votes
Voici une explication
10 votes
Cela a été écrit des milliards de fois. Voir: google.com/search?q=0x5f3759df
17 votes
Merci quand même. C'était une question beaucoup plus intéressante que "comment rendre un nombre positif négatif en C#?"
11 votes
N'était pas Carmack. fr.wikipedia.org/wiki/Fast_inverse_square_root
9 votes
Sacrebleu, c'est juste un hack basé sur la méthode de Newton, ce n'est pas un saint graal des algorithmes, arrêtez d'en parler s'il vous plaît :P
1 votes
Dans cette ligne
i = * ( long * ) &y;
, pourquoi l'adresse de y est-elle prise comme un pointeur vers un long, puis déréférencée à nouveau ?1 votes
@Nubcake : parce que
y
est unfloat
, et qu'il est manipulé en tant qu'entier. (De manière non sûre, car cela viole les règles strictes de liaison de C. Uneunion
en C99, oumemcpy
en C89 / C++ ferait la même chose en suivant les règles du langage, et compilerait de la même manière au moins avec des compilateurs optimisés modernes.)