Je cherchais une méthode efficace pour calculer uneb (dites a = 2
et b = 50
). Pour commencer, j’ai décidé de jeter un coup d’œil sur l’implémentation de la fonction Math.Pow()
. Mais dans .NET Reflector , tout ce que j'ai trouvé était le suivant:
[MethodImpl(MethodImplOptions.InternalCall), SecuritySafeCritical]
public static extern double Pow(double x, double y);
Quelles sont certaines des ressources dans lesquelles je peux voir ce qui se passe à l'intérieur lorsque j'appelle la fonction Math.Pow()
?
MethodImplOptions.InternalCall
Cela signifie que la méthode est réellement implémentée dans le CLR, écrit en C++. Le compilateur juste à temps consulte une table avec des méthodes implémentées en interne et compile directement l'appel à la fonction C++.
L'examen du code nécessite le code source du CLR. Vous pouvez l'obtenir à partir de distribution SSCLI2 . Il a été écrit autour du laps de temps .NET 2.0. J'ai constaté que les implémentations de bas niveau, telles que Math.Pow()
, étaient encore en grande partie précises pour les versions ultérieures du CLR.
La table de recherche est située dans clr/src/vm/ecall.cpp. La section pertinente pour Math.Pow()
ressemble à ceci:
FCFuncStart(gMathFuncs)
FCIntrinsic("Sin", COMDouble::Sin, CORINFO_INTRINSIC_Sin)
FCIntrinsic("Cos", COMDouble::Cos, CORINFO_INTRINSIC_Cos)
FCIntrinsic("Sqrt", COMDouble::Sqrt, CORINFO_INTRINSIC_Sqrt)
FCIntrinsic("Round", COMDouble::Round, CORINFO_INTRINSIC_Round)
FCIntrinsicSig("Abs", &gsig_SM_Flt_RetFlt, COMDouble::AbsFlt, CORINFO_INTRINSIC_Abs)
FCIntrinsicSig("Abs", &gsig_SM_Dbl_RetDbl, COMDouble::AbsDbl, CORINFO_INTRINSIC_Abs)
FCFuncElement("Exp", COMDouble::Exp)
FCFuncElement("Pow", COMDouble::Pow)
// etc..
FCFuncEnd()
La recherche de "COMDouble" vous amène à clr/src/classlibnative/float/comfloat.cpp. Je vous épargne le code, jetez un coup d'oeil par vous-même. En gros, il vérifie la présence de cas, puis appelle la version de pow()
du CRT.
Le seul autre détail d'implémentation intéressant est la macro FCIntrinsic de la table. C'est un indice que la gigue peut implémenter la fonction en tant qu'intrinsèque. En d'autres termes, remplacez l'appel de fonction par une instruction de code machine à virgule flottante. Ce qui n'est pas le cas pour Pow()
, il n'y a pas d'instruction FPU pour cela. Mais certainement pour les autres opérations simples. Il convient de noter que cela peut rendre les calculs en virgule flottante en C # considérablement plus rapides que le même code en C++, cochez cette réponse pour la raison.
En passant, le code source du CRT est également disponible si vous disposez de la version complète du répertoire Visual Studio vc/crt/src. Vous frapperez le mur sur pow()
mais Microsoft a acheté ce code à Intel. Faire un meilleur travail que les ingénieurs d'Intel est peu probable. Bien que l'identité de mon livre de lycée fût deux fois plus rapide quand je l'ai essayée:
public static double FasterPow(double x, double y) {
return Math.Exp(y * Math.Log(x));
}
Mais ce n'est pas un vrai substitut, car il accumule des erreurs provenant de 3 opérations en virgule flottante et ne résout pas les problèmes de domaine bizarres qu'a Pow (). Comme 0 ^ 0 et -Infinity élevé à n’importe quel pouvoir.
Réponse de Hans Passant c'est bien, mais j'aimerais ajouter que si b
est un entier, alors a^b
peut être calculé très efficacement avec une décomposition binaire. Voici une version modifiée de Henry Warren Hacker's Delight :
public static int iexp(int a, uint b) {
int y = 1;
while(true) {
if ((b & 1) != 0) y = a*y;
b = b >> 1;
if (b == 0) return y;
a *= a;
}
}
Il note que cette opération est optimale (effectue le nombre minimal d'opérations arithmétiques ou logiques) pour tous les b <15. Il n'y a pas non plus de solution connue au problème général de la recherche d'une séquence optimale de facteurs pour calculer a^b
pour tout b autre qu'une recherche approfondie. C'est un problème NP-difficile. Donc, fondamentalement, cela signifie que la décomposition binaire est aussi bonne que possible.
Si la version C librement disponible de pow
est une indication, il ne ressemble en rien à ce que vous attendez. Il ne vous serait d'aucune aide de trouver la version .NET, car le problème que vous résolvez (c'est-à-dire celui avec des entiers) est beaucoup plus simple, et peut être résolu en quelques lignes de code C # avec l'exponentiation par l'algorithme de quadrature .