Perché "int pow (int base, int esponente)" non è nelle librerie C ++ standard?


116

Mi sento come se non dovessi riuscire a trovarlo. C'è qualche ragione che il C ++ powfunzione non implementa la funzione di "potere" per qualsiasi cosa, tranne floats e doubles?

So che l'implementazione è banale, mi sento come se stessi facendo un lavoro che dovrebbe essere in una libreria standard. Una robusta funzione di alimentazione (cioè gestisce l'overflow in modo coerente ed esplicito) non è divertente da scrivere.


4
Questa è una buona domanda e non credo che le risposte abbiano molto senso. Gli esponenti negativi non funzionano? Prendi gli interi senza segno come esponenti. La maggior parte degli input lo fa traboccare? Lo stesso vale per exp e double pow, non vedo nessuno lamentarsi. Allora perché questa funzione non è standard?
static_rtti

2
@static_rtti: "Lo stesso vale per exp e double pow" è totalmente falso. Elaborerò nella mia risposta.
Stephen Canon

11
La libreria C ++ standard ha double pow(int base, int exponent)da C ++ 11 (§26.8 [c.math] / 11 punto
elenco

È necessario prendere una decisione tra "l'implementazione è banale" e "non è divertente da scrivere".
Marchese di Lorne,

Risposte:


66

A partire da C++11, casi speciali sono stati aggiunti alla suite di funzioni di alimentazione (e altre). C++11 [c.math] /11afferma, dopo aver elencato tutti i float/double/long doublesovraccarichi (la mia enfasi e parafrasata):

Inoltre, devono esserci sovraccarichi aggiuntivi sufficienti per garantire che, se qualsiasi argomento corrispondente a un doubleparametro ha un tipo doubleo un tipo intero, tutti gli argomenti corrispondenti ai doubleparametri siano effettivamente sottoposti a cast double.

Quindi, in pratica, i parametri interi verranno aggiornati al doppio per eseguire l'operazione.


Prima di C++11(che era quando è stata posta la tua domanda), non esistevano sovraccarichi di numeri interi.

Poiché non ero né strettamente associato ai creatori CC++ai tempi della loro creazione (anche se sono piuttosto vecchio), né facevo parte dei comitati ANSI / ISO che hanno creato gli standard, questa è necessariamente un'opinione da parte mia. Mi piacerebbe pensare che sia un'opinione informata ma, come ti dirà mia moglie (spesso e senza molto incoraggiamento necessario), mi sono sbagliato prima :-)

Supponiamo, per quello che vale, segue.

Ho il sospetto che il motivo per l'originale pre-ANSI Cnon ha avuto questa caratteristica è perché è stato tutto inutile. In primo luogo, c'era già un modo perfettamente buono per eseguire potenze intere (con doppie e poi semplicemente riconvertirle in un intero, controllando l'overflow e l'underflow degli interi prima della conversione).

In secondo luogo, un'altra cosa che devi ricordare è che l'intento originale Cera quello di un linguaggio di programmazione di sistema , ed è discutibile se il punto mobile sia desiderabile in quell'arena.

Poiché uno dei suoi casi d'uso iniziali era quello di codificare UNIX, il virgola mobile sarebbe stato quasi inutile. Anche BCPL, su cui si basava C, non aveva alcuna utilità per i poteri (non aveva affatto virgola mobile, dalla memoria).

Per inciso, un operatore di alimentazione integrale sarebbe probabilmente stato un operatore binario piuttosto che una chiamata di libreria. Non si aggiungono due numeri interi con x = add (y, z)ma con x = y + z- parte del linguaggio appropriato piuttosto che della libreria.

Terzo, poiché l'implementazione del potere integrale è relativamente banale, è quasi certo che gli sviluppatori del linguaggio userebbero meglio il loro tempo fornendo cose più utili (vedi sotto i commenti sul costo opportunità).

Ciò è rilevante anche per l'originale C++. Poiché l'implementazione originale era in effetti solo un traduttore che produceva Ccodice, trasportava molti degli attributi di C. Il suo intento originale era C-con-classi, non C-con-classi-più-un-po '-di-extra-matematica.

Per quanto riguarda il motivo per cui non è mai stato aggiunto agli standard prima C++11, è necessario ricordare che gli organismi di definizione degli standard hanno linee guida specifiche da seguire. Ad esempio, ANSI è Cstato specificamente incaricato di codificare la pratica esistente, non di creare un nuovo linguaggio. Altrimenti avrebbero potuto impazzire e darci Ada :-)

Anche le iterazioni successive di tale standard hanno linee guida specifiche e possono essere trovate nei documenti razionali (motivazione del motivo per cui il comitato ha preso determinate decisioni, non motivazione della lingua stessa).

Ad esempio, il C99documento razionale porta avanti specificamente due dei C89principi guida che limitano ciò che può essere aggiunto:

  • Mantieni la lingua piccola e semplice.
  • Fornisci solo un modo per eseguire un'operazione.

Le linee guida (non necessariamente quelle specifiche ) sono stabilite per i singoli gruppi di lavoro e quindi limitano anche i C++comitati (e tutti gli altri gruppi ISO).

Inoltre, gli organismi di definizione degli standard si rendono conto che esiste un costo opportunità (un termine economico che significa ciò a cui devi rinunciare per una decisione presa) per ogni decisione che prendono. Ad esempio, il costo opportunità di acquistare quella macchina da super-gioco da $ 10.000 è costituito da relazioni cordiali (o probabilmente tutte le relazioni) con l'altra metà per circa sei mesi.

Eric Gunnerson lo spiega bene con la sua spiegazione -100 punti sul perché le cose non vengono sempre aggiunte ai prodotti Microsoft - fondamentalmente una funzionalità inizia da 100 punti nel buco quindi deve aggiungere un bel po 'di valore per essere considerata.

In altre parole, preferiresti avere un operatore di alimentazione integrato (che, onestamente, qualsiasi codificatore decente potrebbe montare in dieci minuti) o il multi-threading aggiunto allo standard? Per quanto mi riguarda, preferirei avere quest'ultimo e non dover perdere tempo con le diverse implementazioni in UNIX e Windows.

Vorrei anche vedere migliaia e migliaia di raccolte della libreria standard (hash, btrees, alberi rosso-neri, dizionario, mappe arbitrarie e così via) ma, come afferma la logica:

Uno standard è un trattato tra implementatore e programmatore.

E il numero di implementatori negli organismi di standardizzazione supera di gran lunga il numero di programmatori (o almeno di quei programmatori che non comprendono il costo opportunità). Se tutte queste cose fossero state aggiunte, il prossimo standard C++sarebbe C++215xe sarebbe probabilmente completamente implementato dagli sviluppatori di compilatori trecento anni dopo.

Comunque, questo è il mio pensiero (piuttosto voluminoso) sull'argomento. Se solo i voti venissero distribuiti in base alla quantità piuttosto che alla qualità, presto farei saltare tutti gli altri dall'acqua. Grazie per aver ascoltato :-)


2
FWIW, non credo che C ++ segua "Fornire solo un modo per eseguire un'operazione" come vincolo. Giustamente, perché ad esempio to_stringe lambda sono entrambi comodità per cose che potresti già fare. Suppongo che si possa interpretare "solo un modo di eseguire un'operazione" in modo molto approssimativo per consentire entrambi, e allo stesso tempo per consentire quasi ogni duplicazione di funzionalità che si possa immaginare, dicendo "aha! No! Perché la comodità fa si tratta di un'operazione leggermente diversa dall'alternativa esattamente equivalente ma più prolissa! ". Il che è certamente vero per i lambda.
Steve Jessop

@Steve, sì, è stato espresso male da parte mia. È più corretto dire che ci sono linee guida per ogni commissione piuttosto che tutte le commissioni seguono le stesse linee guida. Risposta corretta a Clarifyl
paxdiablo

2
Solo un punto (su alcuni): "qualsiasi scimmia in codice potrebbe montare in dieci minuti". Certo, e se 100 scimmie in codice (bel termine offensivo, BTW) lo fanno ogni anno (probabilmente una stima bassa), abbiamo 1000 minuti sprecati. Molto efficiente, non credi?
Jürgen A. Erhard

1
@ Jürgen, non voleva essere un insulto (dato che in realtà non ho attribuito l'etichetta a nessuno in particolare), era solo un'indicazione che pownon richiedeva molta abilità. Certo avrei preferito lo standard di fornire qualcosa che avrebbe bisogno di un sacco di abilità, e il risultato in minuti di gran lunga più sprecati se lo sforzo doveva essere duplicato.
paxdiablo

2
@ eharo2, sostituisci semplicemente "codificatore mezzo decente" nel testo corrente con "scimmia codice". Non pensavo nemmeno che fosse offensivo, ma ho pensato che fosse meglio essere cauti e, ad essere onesti, l'attuale formulazione trasmette la stessa idea.
paxdiablo

41

Per qualsiasi tipo integrale a larghezza fissa, quasi tutte le possibili coppie di input superano comunque il tipo. A che serve standardizzare una funzione che non fornisce un risultato utile per la maggior parte dei suoi possibili input?

È praticamente necessario disporre di un tipo intero grande per rendere la funzione utile, e la maggior parte delle librerie di interi grandi fornisce la funzione.


Modifica: in un commento alla domanda, static_rtti scrive "La maggior parte degli input causa un overflow? Lo stesso vale per exp e double pow, non vedo nessuno che si lamenta." Questo non è corretto.

Lasciamo da parte exp, perché non è questo il punto (anche se in realtà rafforzerebbe il mio caso) e concentriamoci su double pow(double x, double y). Per quale porzione di coppie (x, y) questa funzione fa qualcosa di utile (cioè, non semplicemente overflow o underflow)?

In realtà mi concentrerò solo su una piccola porzione delle coppie di input per cui powha senso, perché sarà sufficiente per dimostrare il mio punto: se x è positivo e | y | <= 1, quindi pownon overflow o underflow. Questo comprende quasi un quarto di tutte le coppie in virgola mobile (esattamente la metà dei numeri in virgola mobile non NaN sono positivi e solo meno della metà dei numeri in virgola mobile non NaN hanno una grandezza inferiore a 1). Ovviamente, ci sono molte altre coppie di input per le quali powproduce risultati utili, ma abbiamo accertato che è almeno un quarto di tutti gli input.

Ora diamo un'occhiata a una funzione di potenza intera a larghezza fissa (cioè non bignum). Per quale porzione di input non trabocca semplicemente? Per massimizzare il numero di coppie di input significative, la base dovrebbe essere con segno e l'esponente senza segno. Supponiamo che la base e l'esponente siano entrambi nlarghi bit. Possiamo facilmente ottenere un limite sulla porzione di input che sono significativi:

  • Se l'esponente 0 o 1, qualsiasi base è significativa.
  • Se l'esponente è 2 o maggiore, nessuna base maggiore di 2 ^ (n / 2) produce un risultato significativo.

Pertanto, delle 2 ^ (2n) coppie di input, meno di 2 ^ (n + 1) + 2 ^ (3n / 2) producono risultati significativi. Se guardiamo a quello che è probabilmente l'uso più comune, gli interi a 32 bit, ciò significa che qualcosa nell'ordine di 1/1000 dell'uno percento delle coppie di input non va semplicemente in overflow.


8
Comunque tutto questo è discutibile. Solo perché una funzione non è valida per alcuni o molti input non la rende meno utile.
static_rtti

2
@static_rtti: pow(x,y)non underflow a zero per qualsiasi x se | y | <= 1. Esiste una banda di input molto ristretta (x grande, y molto vicina a -1) per la quale si verifica un underflow, ma il risultato è ancora significativo in tale intervallo.
Stephen Canon

2
Dopo averci pensato più, concordo sull'underflow. Penso ancora che questo non sia rilevante per la domanda, però.
static_rtti

7
@ybungalobill: Perché avresti scelto questo come motivo? Personalmente, preferirei l'utilità per un gran numero di problemi e programmatori, possibilità di creare versioni ottimizzate per harware più veloci dell'implementazione ingenua che probabilmente scriverà la maggior parte dei programmatori, e così via. Il tuo criterio sembra completamente arbitrario e, ad essere sincero, del tutto inutile.
static_rtti

5
@StephenCanon: Il lato positivo è che la tua argomentazione mostra che l'implementazione ovviamente corretta e ottimale dell'intero powè semplicemente una piccola tabella di ricerca. :-)
R .. GitHub SMETTA DI AIUTARE IL GHIACCIO

11

Perché non c'è comunque modo di rappresentare tutte le potenze intere in un int:

>>> print 2**-4
0.0625

3
Per un tipo numerico di dimensioni finite, non è possibile rappresentare tutte le potenze di quel tipo all'interno di quel tipo a causa dell'overflow. Ma il tuo punto sui poteri negativi è più valido.
Chris Lutz

1
Vedo gli esponenti negativi come qualcosa che un'implementazione standard potrebbe gestire, prendendo un int senza segno come esponente o restituendo zero quando un esponente negativo viene fornito come input e un int è l'output atteso.
Dan O

3
o avere separato int pow(int base, unsigned int exponent)efloat pow(int base, int exponent)
Ponkadoodle

4
Potrebbero semplicemente dichiararlo come comportamento non definito per passare un numero intero negativo.
Johannes Schaub - litb

2
In tutte le implementazioni moderne, qualsiasi cosa oltre int pow(int base, unsigned char exponent)è comunque in qualche modo inutile. O la base è 0 o 1 e l'esponente non ha importanza, è -1, nel qual caso conta solo l'ultimo bit dell'esponente, o base >1 || base< -1in tal caso exponent<256a pena di overflow.
MSalters

9

In realtà è una domanda interessante. Un argomento che non ho trovato nella discussione è la semplice mancanza di ovvi valori di ritorno per gli argomenti. Contiamo i modi in cui la int pow_int(int, int)funzione ipotetica potrebbe fallire.

  1. straripamento
  2. Risultato indefinito pow_int(0,0)
  3. Il risultato non può essere rappresentato pow_int(2,-1)

La funzione ha almeno 2 modalità di guasto. I numeri interi non possono rappresentare questi valori, il comportamento della funzione in questi casi dovrebbe essere definito dallo standard e i programmatori dovrebbero essere consapevoli di come esattamente la funzione gestisce questi casi.

Nel complesso, lasciare la funzione fuori sembra l'unica opzione sensata. Il programmatore può utilizzare la versione in virgola mobile con tutte le segnalazioni di errori disponibili invece.


Ma i primi due casi non si applicherebbero anche a un powbetween float? Prendi due grandi galleggianti, alzane uno alla potenza dell'altro e hai un Overflow. E pow(0.0, 0.0)causerebbe lo stesso problema del tuo secondo punto. Il tuo terzo punto è l'unica vera differenza tra l'implementazione di una funzione di potenza per interi e float.
numbermaniac

7

Risposta breve:

Una specializzazione di pow(x, n)dove nè un numero naturale è spesso utile per le prestazioni temporali . Ma il generico della libreria standard pow()funziona ancora abbastanza ( sorprendentemente! ) Bene per questo scopo ed è assolutamente fondamentale includerne il meno possibile nella libreria C standard in modo che possa essere reso il più portatile e il più facile da implementare possibile. D'altra parte, ciò non impedisce affatto che sia nella libreria standard C ++ o STL, che sono abbastanza sicuro che nessuno ha intenzione di utilizzare in una sorta di piattaforma incorporata.

Ora, per la risposta lunga.

pow(x, n)può essere reso molto più veloce in molti casi specializzandosi nsu un numero naturale. Ho dovuto utilizzare la mia implementazione di questa funzione per quasi tutti i programmi che scrivo (ma scrivo molti programmi matematici in C). L'operazione specializzata può essere eseguita in O(log(n))tempo, ma quando nè piccola, una versione lineare più semplice può essere più veloce. Ecco le implementazioni di entrambi:


    // Computes x^n, where n is a natural number.
    double pown(double x, unsigned n)
    {
        double y = 1;
        // n = 2*d + r. x^n = (x^2)^d * x^r.
        unsigned d = n >> 1;
        unsigned r = n & 1;
        double x_2_d = d == 0? 1 : pown(x*x, d);
        double x_r = r == 0? 1 : x;
        return x_2_d*x_r;
    }
    // The linear implementation.
    double pown_l(double x, unsigned n)
    {
        double y = 1;
        for (unsigned i = 0; i < n; i++)
            y *= x;
        return y;
    }

(Ho lasciato xe il valore restituito raddoppia perché il risultato di pow(double x, unsigned n)si adatterà a un doppio più o meno tutte le volte che pow(double, double)vorrà.)

(Sì, pownè ricorsivo, ma rompere lo stack è assolutamente impossibile poiché la dimensione massima dello stack sarà approssimativamente uguale log_2(n)ed nè un numero intero. Se nè un numero intero a 64 bit, si ottiene una dimensione massima dello stack di circa 64. Nessun hardware ha una dimensione così estrema limitazioni di memoria, ad eccezione di alcuni PIC ingannevoli con stack hardware che vanno solo da 3 a 8 chiamate di funzione in profondità.)

Per quanto riguarda le prestazioni, rimarrai sorpreso da ciò di cui pow(double, double)è capace una varietà da giardino . Ho testato un centinaio di milioni di iterazioni sul mio Thinkpad IBM di 5 anni con xuguale al numero di iterazioni e nuguale a 10. In questo scenario, ha pown_lvinto. glibc ha pow()impiegato 12,0 secondi utente, pown7,4 secondi utente e pown_lsolo 6,5 secondi utente. Quindi non è troppo sorprendente. Più o meno ce lo aspettavamo.

Quindi, ho lasciato xessere costante (l'ho impostato a 2,5) e ho ripetuto nda 0 a 19 cento milioni di volte. Questa volta, del tutto inaspettatamente, glibc ha powvinto, e con una frana! Ci sono voluti solo 2,0 secondi utente. Il mio ha pownimpiegato 9,6 secondi e pown_l12,2 secondi. Cos'è successo qua? Ho fatto un altro test per scoprirlo.

Ho fatto la stessa cosa di sopra solo con xpari a un milione. Questa volta, ha pownvinto a 9.6 secondi. pown_lha preso 12.2s e glibc pow ha preso 16.3s. Ora è chiaro! glibc powfunziona meglio dei tre quando xè basso, ma peggio quando xè alto. Quando xè alto, pown_lfunziona al meglio quando nè basso e pownfunziona meglio quando xè alto.

Quindi ecco tre diversi algoritmi, ciascuno in grado di funzionare meglio degli altri nelle giuste circostanze. Quindi, in definitiva, quale utilizzare molto probabilmente dipende da come si sta pensando di usare pow, ma usando la versione corretta è valsa la pena, e aventi tutte le versioni è bello. In effetti, potresti persino automatizzare la scelta dell'algoritmo con una funzione come questa:

double pown_auto(double x, unsigned n, double x_expected, unsigned n_expected) {
    if (x_expected < x_threshold)
        return pow(x, n);
    if (n_expected < n_threshold)
        return pown_l(x, n);
    return pown(x, n);
}

Fintanto che x_expectede n_expectedsono costanti decise in fase di compilazione, insieme a eventuali altri avvertimenti, un compilatore di ottimizzazione degno di questo nome rimuoverà automaticamente l'intera pown_autochiamata di funzione e la sostituirà con la scelta appropriata dei tre algoritmi. (Ora, se hai davvero intenzione di tentare di usarlo , probabilmente dovrai giocarci un po ', perché non ho esattamente provato a compilare ciò che avevo scritto sopra.;))

D'altra parte, glibc pow funziona e glibc è già abbastanza grande. Lo standard C dovrebbe essere portabile, anche su vari dispositivi embedded (infatti gli sviluppatori embedded ovunque generalmente concordano sul fatto che glibc è già troppo grande per loro), e non può essere portabile se per ogni semplice funzione matematica deve includere ogni algoritmo alternativo che potrebbe essere utile. Quindi, ecco perché non è nello standard C.

nota a piè di pagina: Durante il test delle prestazioni temporali, ho fornito alle mie funzioni flag di ottimizzazione relativamente generosi ( -s -O2) che potrebbero essere paragonabili, se non peggiori, a ciò che è stato probabilmente usato per compilare glibc sul mio sistema (archlinux), quindi i risultati sono probabilmente giusto. Per un test più rigoroso, dovrei compilare glibc da solo e davvero non ho voglia di farlo. Usavo Gentoo, quindi ricordo quanto tempo ci vuole, anche quando l'attività è automatizzata . I risultati sono abbastanza conclusivi (o piuttosto inconcludenti) per me. Ovviamente sei il benvenuto a farlo da solo.

Bonus round: una specializzazione di pow(x, n)tutti i numeri interi è fondamentale se è richiesto un output intero esatto, cosa che accade. Considera l'allocazione della memoria per un array N-dimensionale con p ^ N elementi. Ottenere p ^ N fuori anche solo da uno risulterà in un possibile segfault che si verifica in modo casuale.


Immagino che se ti sbarazzi della ricorsione, risparmierai il tempo necessario per l'allocazione dello stack. E sì, abbiamo avuto una situazione in cui il pow rallentava tutto e dobbiamo implementare il nostro pow.
Sambatyon

"Nessuno ha limiti di memoria così estremi" è falso. I PIC hanno spesso uno stack di chiamate limitato per un massimo di 3 (l'esempio è PIC10F200) a 8 (l'esempio è 16F722A) chiamate (PIC usa uno stack hardware per le chiamate di funzione).
12431234123412341234123

oh, amico, è brutale lol. OK, quindi non funzionerà su quei PIC.
enigmaticPhysicist

Per una base intera oltre che per la potenza, come la domanda si pone, i compilatori (gcc e clang) produrranno facilmente un ciclo senza rami da un'implementazione iterativa (invece che ricorsiva). Ciò evita errori di previsione del ramo da ogni bit di n. godbolt.org/z/L9Kb98 . gcc e clang non riescono a ottimizzare la tua definizione ricorsiva in un semplice ciclo, e in realtà si ramificano su ogni bit di n. (Poiché pown_iter(double,unsigned)continuano a ramificarsi, ma dovrebbe essere possibile un'implementazione SSE2 o SSE4.1 senza ramificazioni in x86 asm o con intrinseci C. Ma anche questo è meglio della ricorsione)
Peter Cordes

Merda, ora devo fare di nuovo i benchmark con una versione basata su loop solo per essere sicuro. Ci penserò.
EnigmaticPhysicist


3

Il mondo è in continua evoluzione e così sono i linguaggi di programmazione. La quarta parte del decimale C TR ¹ aggiunge altre funzioni a <math.h>. Due famiglie di queste funzioni possono essere di interesse per questa domanda:

  • Le pownfunzioni, che prendono un numero in virgola mobile e un intmax_tesponente.
  • Le powrfunzioni, che prendono due numeri in virgola mobile ( xe y) e calcolano xalla potenza ycon la formula exp(y*log(x)).

Sembra che i ragazzi standard alla fine abbiano ritenuto queste funzionalità abbastanza utili da essere integrate nella libreria standard. Tuttavia, la logica è che queste funzioni sono consigliate dallo standard ISO / IEC / IEEE 60559: 2011 per i numeri binari e decimali in virgola mobile. Non posso dire con certezza quale "standard" sia stato seguito al tempo di C89, ma le future evoluzioni di <math.h>saranno probabilmente fortemente influenzate dalle future evoluzioni dello standard ISO / IEC / IEEE 60559 .

Si noti che la quarta parte del TR decimale non sarà inclusa in C2x (la prossima revisione C principale) e sarà probabilmente inclusa in seguito come funzionalità opzionale. Non ho avuto intenzione di includere questa parte del TR in una futura revisione C ++.


¹ È possibile trovare una documentazione in fase di elaborazione qui .


Esistono implementazioni plausibili in cui l'utilizzo powncon un esponente maggiore di quanto LONG_MAXdovrebbe mai produrre un valore diverso dall'utilizzo LONG_MAX, o in cui un valore inferiore a quello LONG_MINdovrebbe produrre un valore diverso da LONG_MIN? Mi chiedo quale vantaggio si ottiene utilizzando intmax_tper un esponente?
supercat

@supercat Nessuna idea, mi dispiace.
Morwenn

Potrebbe essere utile ricordare che, guardando lo Standard, sembra definire anche una funzione opzionale "crpown" che, se definita, sarebbe una versione correttamente arrotondata di "pown"; altrimenti lo Standard non specifica il grado di accuratezza richiesto. L'implementazione di un "pown" veloce e moderatamente preciso è facile, ma garantire un arrotondamento corretto in tutti i casi può essere molto più costoso.
supercat

2

Forse perché l'ALU del processore non ha implementato una tale funzione per gli interi, ma esiste un'istruzione FPU di questo tipo (come sottolinea Stephen, in realtà è una coppia). Quindi è stato effettivamente più veloce eseguire il cast per raddoppiare, chiamare pow con il doppio, quindi testare l'overflow e il cast indietro, piuttosto che implementarlo usando l'aritmetica dei numeri interi.

(per prima cosa, i logaritmi riducono i poteri alla moltiplicazione, ma i logaritmi degli interi perdono molta precisione per la maggior parte degli input)

Stephen ha ragione che sui processori moderni questo non è più vero, ma lo standard C quando sono state selezionate le funzioni matematiche (C ++ ha usato solo le funzioni C) ora ha 20 anni?


5
Non conosco nessuna architettura attuale con un'istruzione FPU per pow. x86 ha y log2 xun'istruzione ( fyl2x) che può essere utilizzata come prima parte di una powfunzione, ma una powfunzione scritta in questo modo richiede centinaia di cicli per essere eseguita sull'hardware corrente; una routine di esponenziazione intera ben scritta è molte volte più veloce.
Stephen Canon

Non so che "centinaia" sia accurato, sembra essere circa 150 cicli per fyl2x e poi f2xm1 sulla maggior parte delle CPU moderne e questo viene collegato ad altre istruzioni. Ma hai ragione sul fatto che un'implementazione di interi ben sintonizzati dovrebbe essere molto più veloce (di questi tempi) poiché IMUL è stato velocizzato molto più delle istruzioni in virgola mobile. Ai tempi in cui è stato scritto lo standard C, tuttavia, IMUL era piuttosto costoso e utilizzarlo in un ciclo probabilmente richiedeva più tempo rispetto all'utilizzo dell'FPU.
Ben Voigt

2
Ha cambiato il mio voto alla luce della correzione; tuttavia, tieni presente (a) che lo standard C è stato sottoposto a una revisione importante (inclusa una grande espansione della libreria matematica) nel 1999 e (b) che lo standard C non è scritto su alcuna architettura di processore specifica - la presenza o l'assenza di istruzioni FPU su x86 non ha essenzialmente nulla a che fare con la funzionalità che il comitato C sceglie di standardizzare.
Stephen Canon

Non è legato a nessuna architettura, vero, ma il costo relativo dell'interpolazione di una tabella di ricerca (generalmente utilizzata per l'implementazione in virgola mobile) rispetto al moltiplicatore intero è cambiato praticamente allo stesso modo per tutte le architetture immagino.
Ben Voigt

1

Ecco un'implementazione O (log (n)) di pow () davvero semplice che funziona per qualsiasi tipo numerico, inclusi gli interi :

template<typename T>
static constexpr inline T pown(T x, unsigned p) {
    T result = 1;

    while (p) {
        if (p & 0x1) {
            result *= x;
        }
        x *= x;
        p >>= 1;
    }

    return result;
}

È meglio dell'implementazione O (log (n)) di enigmaticPhysicist perché non usa la ricorsione.

È anche quasi sempre più veloce della sua implementazione lineare (purché p> ~ 3) perché:

  • non richiede memoria aggiuntiva
  • fa solo ~ 1,5 volte più operazioni per ciclo
  • fa solo ~ 1,25 volte più aggiornamenti di memoria per ciclo

-2

È un dato di fatto, lo fa.

Poiché C ++ 11 esiste un'implementazione basata su modelli di pow(int, int)--- e casi ancora più generali, vedere (7) in http://en.cppreference.com/w/cpp/numeric/math/pow


EDIT: i puristi potrebbero obiettare che questo non è corretto, poiché in realtà viene utilizzata la digitazione "promossa". In un modo o nell'altro, si ottiene un intrisultato corretto o un errore sui intparametri.


2
questo non è corretto. L'overload (7) è quello a pow ( Arithmetic1 base, Arithmetic2 exp )cui verrà eseguito il cast doubleo long doublese hai letto la descrizione: "7) Un insieme di overload o un modello di funzione per tutte le combinazioni di argomenti di tipo aritmetico non coperti da 1-3). Se qualsiasi argomento ha un tipo integrale, viene convertito in double. Se un argomento è long double, anche il tipo restituito Promoted è long double, altrimenti il ​​tipo restituito è sempre double. "
phuclv

cosa c'è di sbagliato qui? Ho solo detto che oggigiorno (da C ++ 11) un modello pow ( , ) è nella libreria standard, cosa che non era il caso nel 2010.
Dima Pasechnik

5
No, non lo è. I Templeates promuovono questi tipi a double o long double. Quindi funziona sui doppi sotto.
Trismegistos

1
@Trismegistos Permette ancora i parametri int. Se questo modello non fosse presente, il passaggio di parametri int fa sì che interpreti i bit nell'int come un float, causando risultati arbitrari imprevisti. Lo stesso accade con valori di input misti. ad esempio pow(1.5f, 3)= 1072693280ma pow(1.5f, float(3))=3.375
Mark Jeronimus

2
L'OP richiesto int pow(int, int), ma C ++ 11 fornisce solo double pow(int, int). Vedi la spiegazione di @phuclv.
xuhdev

-4

Un motivo molto semplice:

5^-2 = 1/25

Tutto nella libreria STL si basa sulle cose più accurate e robuste che si possano immaginare. Certo, l'int tornerebbe a zero (da 1/25) ma questa sarebbe una risposta imprecisa.

Sono d'accordo, è strano in alcuni casi.


3
È ovviamente necessario richiedere un secondo argomento non firmato. Esistono molte applicazioni che richiedono solo potenze intere non negative.
EnigmaticPhysicist
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.