Come viene implementato Math.Pow () in .NET Framework?


433

Stavo cercando un approccio efficiente per il calcolo di una b (dire a = 2e b = 50). Per iniziare, ho deciso di dare un'occhiata all'implementazione della Math.Pow()funzione. Ma in .NET Reflector , tutto ciò che ho trovato è stato questo:

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

Quali sono alcune delle risorse in cui posso vedere cosa succede dentro quando chiamo Math.Pow()funzione?


15
Proprio come una FYI, se sei confuso sull'intero InternalCallcon un externmodificatore (poiché sembrano essere in conflitto), vedi la domanda (e le risposte risultanti) che ho pubblicato su questa stessa cosa.
CraigTP,

6
Per 2^xun'operazione se xè intero, il risultato è un'operazione a turni. Quindi forse potresti costruire il risultato usando una mantissa di 2e un esponente di x.
Ja72,

@SurajJain il tuo commento è in realtà una domanda che devi pubblicare separatamente.
Ja72,

@SurajJain Sono d'accordo con te. Non sono un moderatore, quindi non posso fare molto qui. Forse la domanda di downvote può essere posta a meta.stackoverflow.com
ja72

Risposte:


855

MethodImplOptions.InternalCall

Ciò significa che il metodo è effettivamente implementato nel CLR, scritto in C ++. Il compilatore just-in-time consulta una tabella con metodi implementati internamente e compila direttamente la chiamata alla funzione C ++.

Uno sguardo al codice richiede il codice sorgente per il CLR. Puoi ottenerlo dalla distribuzione SSCLI20 . È stato scritto intorno al lasso di tempo di .NET 2.0, ho trovato le implementazioni di basso livello, come se Math.Pow()fossero ancora in gran parte accurate per le versioni successive del CLR.

La tabella di ricerca si trova in clr / src / vm / ecall.cpp. La sezione pertinente Math.Pow()è simile alla seguente:

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 ricerca di "COMDouble" ti porta a clr / src / classlibnative / float / comfloat.cpp. Ti risparmierò il codice, dai solo un'occhiata. Fondamentalmente controlla la presenza di casi angolari, quindi chiama la versione di CRT di pow().

L'unico altro dettaglio di implementazione interessante è la macro FCIntrinsic nella tabella. Questo è un suggerimento che il jitter può implementare la funzione come intrinseca. In altre parole, sostituire la chiamata di funzione con un'istruzione di codice macchina a virgola mobile. Non è questo il caso Pow(), non ci sono istruzioni FPU per questo. Ma certamente per le altre semplici operazioni. Notevole è che questo può rendere la matematica in virgola mobile in C # sostanzialmente più veloce dello stesso codice in C ++, controlla questa risposta per il motivo.

A proposito, il codice sorgente per CRT è disponibile anche se si dispone della versione completa della directory vc / crt / src di Visual Studio. Colpirai pow()comunque, Microsoft ha acquistato quel codice da Intel. Fare un lavoro migliore degli ingegneri Intel è improbabile. Anche se l'identità del mio libro del liceo era due volte più veloce quando l'ho provato:

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

Ma non è un vero sostituto perché accumula errori da 3 operazioni in virgola mobile e non affronta i problemi del dominio strano che Pow () ha. Come 0 ^ 0 e -Infinity elevato a qualsiasi potenza.


437
Ottima risposta, StackOverflow ha bisogno di più di questo genere di cose, invece di "Perché dovresti saperlo?" succede troppo spesso.
Tom W,

16
@Blue - Non lo so, a parte prendere in giro gli ingegneri Intel. Il mio libro di scuola superiore ha un problema nel sollevare qualcosa al potere di un integrale negativo. Pow (x, -2) è perfettamente calcolabile, Pow (x, -2.1) non è definito. I problemi di dominio sono una stronza da affrontare.
Hans Passant,

12
@ BlueRaja-DannyPflughoeft: molti sforzi vengono fatti per cercare di assicurare che le operazioni in virgola mobile siano il più vicino possibile al valore correttamente arrotondato. powè notoriamente difficile da attuare con precisione, essendo una funzione trascendentale (vedi il dilemma del produttore di tavoli ). È molto più facile con un potere integrale.
Porges il

9
@Hans Passant: Perché Pow (x, -2.1) sarebbe indefinito? Matematicamente pow è definito ovunque per tutte le xe y. Tendi a ottenere numeri complessi per x negativi e non interi y.
Jules,

8
@Jules pow (0, 0) non è definito.
Rilievo

110

La risposta di Hans Passant è ottima, ma vorrei aggiungere che se bè un numero intero, a^bpuò essere calcolato in modo molto efficiente con la decomposizione binaria. Ecco una versione modificata da Henry Warren's 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;
    }    
}

Egli osserva che questa operazione è ottimale (esegue il numero minimo di operazioni aritmetiche o logiche) per tutti i b <15. Inoltre non esiste una soluzione nota al problema generale di trovare una sequenza ottimale di fattori da calcolare a^bper qualsiasi b diverso da un esteso ricerca. È un problema NP-difficile. Quindi, fondamentalmente, ciò significa che la decomposizione binaria è ottima.


11
Questo algoritmo ( quadrato e moltiplicato ) si applica anche se aè un numero in virgola mobile.
CodesInChaos,

14
In pratica è possibile fare un po 'meglio del quadrato e moltiplicare nativi. Ad esempio, preparare tabelle di ricerca per piccoli esponenti in modo da poter quadrare più volte e solo successivamente moltiplicare o costruire catene di addizione quadrata ottimizzate per esponenti fissi. Questo tipo di problema è parte integrante di importanti algoritmi crittografici, quindi c'è stato un bel po 'di lavoro per ottimizzarlo. La durezza NP riguarda solo gli asintotici nel caso peggiore , spesso siamo in grado di produrre soluzioni ottimali o quasi ottimali per i casi che si presentano nella pratica.
CodesInChaos,

Il testo non menziona l' aessere un numero intero, ma il codice lo fa. Di conseguenza, mi chiedo l' accuratezza del risultato del calcolo "molto efficiente" del testo.
Andrew Morton,

69

Se la versione C disponibile gratuitamentepow è un'indicazione, non assomiglia a qualcosa che ti aspetteresti. Non ti sarebbe di grande aiuto trovare la versione .NET, perché il problema che stai risolvendo (cioè quello con numeri interi) è più semplice per ordini di grandezza, e può essere risolto in poche righe di codice C # con l'esponenziazione dalla quadratura dell'algoritmo .


Grazie per la tua risposta. Il primo collegamento mi ha sorpreso poiché non mi aspettavo un'implementazione tecnica così massiccia della funzione Pow (). Sebbene la risposta di Hans Passant confermi che è la stessa cosa anche nel mondo .Net. Penso di poter risolvere il problema a portata di mano facendo uso di alcune delle tecniche elencate nel link dell'algoritmo di squadratura. Grazie ancora.
Pawan Mishra,

2
Non credo che questo codice sia efficiente. 30 variabili locali dovrebbero solo cancellare tutti i registri. Suppongo solo che sia la versione ARM, ma su x86 30 variabili locali nel metodo sono fantastiche.
Alex Zhukovskiy,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.