¿Cómo se implementa Math.Pow () en .NET Framework?

Estaba buscando un enfoque eficiente para calcular una b (digamos a = 2 b = 50 ). Para comenzar, decidí echar un vistazo a la implementación de la función Math.Pow() . Pero en .NET Reflector , todo lo que encontré fue esto:

 [MethodImpl(MethodImplOptions.InternalCall), SecuritySafeCritical] public static extern double Pow(double x, double y); 

¿Cuáles son algunos de los recursos en los que puedo ver lo que está sucediendo dentro cuando llamo a la función Math.Pow() ?

MethodImplOptions.InternalCall

Eso significa que el método se implementa realmente en el CLR, escrito en C ++. El comstackdor just-in-time consulta una tabla con métodos implementados internamente y comstack directamente la llamada a la función C ++.

Tener un vistazo al código requiere el código fuente para el CLR. Puede obtenerlo de la distribución SSCLI20 . Fue escrito alrededor del marco de tiempo de .NET 2.0, encontré que las implementaciones de bajo nivel, como Math.Pow() siguen siendo en gran medida precisas para las versiones posteriores de la CLR.

La tabla de búsqueda se encuentra en clr / src / vm / ecall.cpp. La sección que es relevante para Math.Pow() ve así:

 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 búsqueda de “COMDouble” lo lleva a clr / src / classlibnative / float / comfloat.cpp. Te ahorraré el código, solo échale un vistazo. Básicamente, verifica los casos de esquina, luego llama a la versión CRT de pow() .

El único otro detalle de implementación que es interesante es la macro FCIntrinsic en la tabla. Esa es una pista de que el jitter puede implementar la función como algo intrínseco. En otras palabras, sustituya la llamada de función por una instrucción de código de máquina de punto flotante. Lo cual no es el caso de Pow() , no hay instrucciones FPU para ello. Pero sin duda para las otras operaciones simples. Es notable que esto puede hacer matemática de punto flotante en C # sustancialmente más rápido que el mismo código en C ++, verifique esta respuesta por la razón por la cual.

Por cierto, el código fuente del CRT también está disponible si tiene la versión completa del directorio de Visual Studio vc / crt / src. Aunque golpearás la pared en pow() , Microsoft compró ese código de Intel. Hacer un mejor trabajo que los ingenieros de Intel es poco probable. Aunque la identidad de mi libro de secundaria fue dos veces más rápida cuando lo intenté:

 public static double FasterPow(double x, double y) { return Math.Exp(y * Math.Log(x)); } 

Pero no es un verdadero sustituto porque acumula errores de 3 operaciones de coma flotante y no se ocupa de los problemas de dominio raro que tiene Pow (). Como 0 ^ 0 y -Infinito elevado a cualquier potencia.

La respuesta de Hans Passant es excelente, pero me gustaría añadir que si b es un número entero, entonces a^b se puede calcular de manera muy eficiente con la descomposición binaria. Aquí hay una versión modificada de Hacker’s Delight de Henry Warren:

 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; } } 

Señala que esta operación es óptima (realiza el número mínimo de operaciones aritméticas o lógicas) para todos los b <15. Además, no se conoce una solución al problema general de encontrar una secuencia óptima de factores para calcular a^b para cualquier otro b que una búsqueda extensa. Es un problema NP-Hard. Básicamente eso significa que la descomposición binaria es tan buena como se consigue.

Si la versión C de pow disponible libremente es una indicación, no se parece a nada que esperas. No sería de mucha ayuda encontrar la versión de .NET, porque el problema que está resolviendo (es decir, el que tiene enteros) es más simple en órdenes de magnitud y se puede resolver en unas pocas líneas de código C # con la exponenciación al cuadrar el algoritmo .