Ich habe das Gefühl, ich muss es einfach nicht finden können. Gibt es einen Grund, warum die C ++ - pow
Funktion die "Power" -Funktion nur für float
s und double
s implementiert ?
Ich weiß, dass die Implementierung trivial ist. Ich habe nur das Gefühl, dass ich Arbeiten mache, die in einer Standardbibliothek sein sollten. Eine robuste Power-Funktion (dh sie behandelt den Überlauf auf konsistente, explizite Weise) macht keinen Spaß beim Schreiben.
double pow(int base, int exponent)
seit C ++ 11 (§26.8 [c.math] / 11 Aufzählungspunkt 2)Antworten:
Ab
C++11
sofort wurden Sonderfälle zur Reihe der Leistungsfunktionen (und anderer) hinzugefügt.C++11 [c.math] /11
stellt fest, nachdem allefloat/double/long double
Überladungen aufgelistet wurden (meine Betonung und umschrieben):Im Grunde genommen werden ganzzahlige Parameter auf doppelt aktualisiert, um die Operation auszuführen.
Vor
C++11
(als Ihre Frage gestellt wurde) gab es keine ganzzahligen Überladungen.Da ich weder eng mit den Erstellern
C
nochC++
in den Tagen ihrer Gründung verbunden war (obwohl ich ziemlich alt bin ), noch Teil der ANSI / ISO-Komitees, die die Standards erstellt haben, ist dies notwendigerweise eine Meinung von meiner Seite. Ich würde gerne denken, dass es eine informierte Meinung ist, aber wie meine Frau Ihnen sagen wird (häufig und ohne viel Ermutigung), habe ich mich vorher geirrt :-)Es folgt die Annahme, was es wert ist.
Ich vermute, dass der Grund, warum das ursprüngliche Pre-ANSI
C
diese Funktion nicht hatte, darin besteht, dass es völlig unnötig war. Zuerst gab es bereits eine sehr gute Möglichkeit, ganzzahlige Potenzen zu erstellen (mit Doppelwerten und dann einfach wieder in eine Ganzzahl umzuwandeln, vor der Konvertierung auf Ganzzahlüberlauf und -unterlauf zu prüfen).Zweitens, eine andere Sache , die Sie dürfen nicht vergessen, ist , dass die ursprüngliche Absicht
C
als war Systeme Sprache Programmierung, und es ist fraglich , ob Floating - Point in dieser Arena überhaupt wünschenswert ist.Da einer der ersten Anwendungsfälle darin bestand, UNIX zu codieren, wäre der Gleitkomma nahezu unbrauchbar gewesen. BCPL, auf dem C basierte, hatte auch keine Verwendung für Potenzen (es hatte überhaupt keinen Gleitkomma aus dem Speicher).
Drittens, da die Implementierung integraler Leistung relativ trivial ist, ist es fast sicher, dass die Entwickler der Sprache ihre Zeit besser nutzen würden, um nützlichere Dinge bereitzustellen (siehe unten Kommentare zu Opportunitätskosten).
Das ist auch für das Original relevant
C++
. Da die ursprüngliche Implementierung praktisch nur ein Übersetzer war, derC
Code produzierte , übertrug sie viele der Attribute vonC
. Seine ursprüngliche Absicht war C-mit-Klassen, nicht C-mit-Klassen-plus-ein-bisschen-extra-Mathe-Zeug.Um zu verstehen, warum es noch nie zuvor zu den Standards hinzugefügt wurde
C++11
, müssen Sie bedenken, dass die Normungsgremien spezifische Richtlinien befolgen müssen. Zum Beispiel wurde ANSIC
speziell beauftragt, bestehende Praktiken zu kodifizieren und keine neue Sprache zu erstellen. Sonst hätten sie verrückt werden und uns Ada geben können :-)Spätere Iterationen dieses Standards haben auch spezifische Richtlinien und können in den Begründungsdokumenten gefunden werden (Begründung, warum der Ausschuss bestimmte Entscheidungen getroffen hat, nicht Begründung für die Sprache selbst).
Zum Beispiel enthält das
C99
Begründungsdokument speziell zwei derC89
Leitprinzipien, die einschränken, was hinzugefügt werden kann:Richtlinien (nicht unbedingt diese spezifischen ) sind für die einzelnen Arbeitsgruppen festgelegt und beschränken daher auch die
C++
Ausschüsse (und alle anderen ISO-Gruppen).Darüber hinaus erkennen die Normungsgremien, dass für jede Entscheidung, die sie treffen, Opportunitätskosten (ein wirtschaftlicher Begriff, auf den Sie bei einer getroffenen Entscheidung verzichten müssen) anfallen. Zum Beispiel sind die Opportunitätskosten für den Kauf dieses 10.000-Dollar-Über-Spielautomaten herzliche Beziehungen (oder wahrscheinlich alle Beziehungen) zu Ihrer anderen Hälfte für etwa sechs Monate.
Eric Gunnerson erklärt dies gut mit seiner Erklärung von -100 Punkten , warum Dinge nicht immer zu Microsoft-Produkten hinzugefügt werden. Grundsätzlich beginnt eine Funktion mit 100 Punkten in der Lücke, sodass sie einen erheblichen Mehrwert bieten muss, um überhaupt berücksichtigt zu werden.
Mit anderen Worten, hätten Sie lieber einen integrierten Stromversorger (den ehrlich gesagt jeder halbwegs anständige Codierer in zehn Minuten aufpeppen könnte) oder Multithreading zum Standard hinzugefügt? Für mich selbst würde ich lieber Letzteres haben und mich nicht mit den unterschiedlichen Implementierungen unter UNIX und Windows herumschlagen müssen.
Ich würde auch gerne Tausende und Abertausende von Sammlungen in der Standardbibliothek sehen (Hashes, Bäume, rot-schwarze Bäume, Wörterbuch, beliebige Karten usw.), aber wie die Begründung besagt:
Und die Anzahl der Implementierer in den Normungsgremien überwiegt bei weitem die Anzahl der Programmierer (oder zumindest der Programmierer, die die Opportunitätskosten nicht verstehen). Wenn all diese Dinge hinzugefügt würden,
C++
wäreC++215x
und würde der nächste Standard dreihundert Jahre später von Compiler-Entwicklern vollständig implementiert.Wie auch immer, das sind meine (ziemlich umfangreichen) Gedanken zu diesem Thema. Wenn nur Stimmen eher nach Quantität als nach Qualität abgegeben würden, würde ich bald alle anderen aus dem Wasser sprengen. Danke fürs Zuhören :-)
quelle
to_string
und Lambdas sind beide Annehmlichkeiten für Dinge, die Sie bereits tun könnten. Ich nehme an, man könnte "nur einen Weg, eine Operation durchzuführen" sehr locker interpretieren , um beide zuzulassen und gleichzeitig fast jede Duplikation von Funktionen zuzulassen, die man sich vorstellen kann, indem man "aha! Nein!" Sagt, weil die Bequemlichkeit dies macht es ist eine subtil andere Operation als die genau äquivalente, aber langatmigere Alternative! ". Was sicherlich für Lambdas gilt.pow
der nicht wirklich viel Geschick erfordert. Sicherlich würde ich eher der Standard etwas schaffen , das würde viel Geschick erfordern, und das Ergebnis in weit mehr verschwendeten Minuten , wenn der Aufwand verdoppelt werden mußte.Bei jedem Integraltyp mit fester Breite laufen ohnehin fast alle möglichen Eingangspaare über den Typ hinaus. Was nützt es, eine Funktion zu standardisieren, die für die überwiegende Mehrheit ihrer möglichen Eingaben kein nützliches Ergebnis liefert?
Sie benötigen so ziemlich einen großen Ganzzahltyp, um die Funktion nützlich zu machen, und die meisten großen Ganzzahlbibliotheken bieten die Funktion.
Bearbeiten: In einem Kommentar zu der Frage schreibt static_rtti: "Die meisten Eingaben verursachen einen Überlauf? Dasselbe gilt für exp und double pow. Ich sehe niemanden, der sich beschwert." Das ist falsch.
Lassen wir es beiseite
exp
, denn das ist nebensächlich (obwohl es meinen Fall tatsächlich stärker machen würde), und konzentrieren wir uns daraufdouble pow(double x, double y)
. Für welchen Teil von (x, y) Paaren macht diese Funktion etwas Nützliches (dh nicht einfach Überlauf oder Unterlauf)?Ich werde mich eigentlich nur auf einen kleinen Teil der Eingabepaare konzentrieren,
pow
was sinnvoll ist, da dies ausreicht, um meinen Standpunkt zu beweisen: Wenn x positiv ist und | y | <= 1,pow
läuft dann nicht über oder unter. Dies umfasst fast ein Viertel aller Gleitkomma-Paare (genau die Hälfte der Nicht-NaN-Gleitkommazahlen ist positiv, und nur weniger als die Hälfte der Nicht-NaN-Gleitkommazahlen hat eine Größe von weniger als 1). Natürlich gibt es viele andere Eingabepaare, für diepow
nützliche Ergebnisse erzielt werden, aber wir haben festgestellt, dass es mindestens ein Viertel aller Eingaben ist.Betrachten wir nun eine ganzzahlige Potenzfunktion mit fester Breite (dh ohne Bignum). Für welche Teileingänge läuft es nicht einfach über? Um die Anzahl der aussagekräftigen Eingabepaare zu maximieren, sollte die Basis signiert und der Exponent vorzeichenlos sein. Angenommen, die Basis und der Exponent sind beide
n
Bits breit. Wir können leicht eine Grenze für den Teil der Eingaben bekommen, die sinnvoll sind:Somit führen von den 2 ^ (2n) -Eingangspaaren weniger als 2 ^ (n + 1) + 2 ^ (3n / 2) zu aussagekräftigen Ergebnissen. Wenn wir uns die wahrscheinlich am häufigsten verwendete Verwendung von 32-Bit-Ganzzahlen ansehen, bedeutet dies, dass etwas in der Größenordnung von 1/1000 von einem Prozent der Eingabepaare nicht einfach überläuft.
quelle
pow(x,y)
läuft für kein x auf Null, wenn | y | <= 1. Es gibt ein sehr schmales Band von Eingängen (großes x, y sehr nahe -1), für das ein Unterlauf auftritt, aber das Ergebnis ist in diesem Bereich immer noch aussagekräftig.pow
einfach eine winzige Nachschlagetabelle ist. :-)Weil es sowieso keine Möglichkeit gibt, alle ganzzahligen Potenzen in einem int darzustellen:
quelle
int pow(int base, unsigned int exponent)
undfloat pow(int base, int exponent)
int pow(int base, unsigned char exponent)
sowieso etwas nutzlos. Entweder ist die Basis 0 oder 1, und der Exponent spielt keine Rolle, er ist -1. In diesem Fall ist nur das letzte Exponentenbit von Bedeutung, oderbase >1 || base< -1
in diesem Fallexponent<256
bei Bestrafung des Überlaufs.Das ist eigentlich eine interessante Frage. Ein Argument, das ich in der Diskussion nicht gefunden habe, ist das einfache Fehlen offensichtlicher Rückgabewerte für die Argumente. Zählen wir, wie die hypthetische
int pow_int(int, int)
Funktion versagen könnte.pow_int(0,0)
pow_int(2,-1)
Die Funktion verfügt über mindestens 2 Fehlermodi. Ganzzahlen können diese Werte nicht darstellen, das Verhalten der Funktion müsste in diesen Fällen vom Standard definiert werden - und Programmierer müssten wissen, wie genau die Funktion diese Fälle behandelt.
Insgesamt scheint es die einzig sinnvolle Option zu sein, die Funktion wegzulassen. Der Programmierer kann stattdessen die Gleitkommaversion mit allen verfügbaren Fehlerberichten verwenden.
quelle
pow
Zwischenschwimmer gelten ? Nehmen Sie zwei große Schwimmer, heben Sie einen auf die Kraft des anderen und Sie haben einen Überlauf. Undpow(0.0, 0.0)
würde das gleiche Problem verursachen wie Ihr 2. Punkt. Ihr dritter Punkt ist der einzige wirkliche Unterschied zwischen der Implementierung einer Potenzfunktion für Ganzzahlen und Floats.Kurze Antwort:
Eine Spezialisierung
pow(x, n)
, won
eine natürliche Zahl ist oft nützlich für Pünktlichkeit . Aber das Generikum der Standardbibliothekpow()
funktioniert für diesen Zweck immer noch ziemlich ( überraschend! ) Gut und es ist absolut wichtig, so wenig wie möglich in die Standard-C-Bibliothek aufzunehmen, damit sie so portabel und so einfach wie möglich implementiert werden kann. Auf der anderen Seite hindert das sie überhaupt nicht daran, in der C ++ - Standardbibliothek oder der STL zu sein, und ich bin mir ziemlich sicher, dass niemand vorhat, sie in einer Art eingebetteter Plattform zu verwenden.Nun zur langen Antwort.
pow(x, n)
kann in vielen Fällen viel schneller gemacht werden, indem man sichn
auf eine natürliche Zahl spezialisiert. Ich musste für fast jedes Programm, das ich schreibe, meine eigene Implementierung dieser Funktion verwenden (aber ich schreibe viele mathematische Programme in C). Die spezialisierte Operation kann rechtzeitig durchgeführtO(log(n))
werden, aber wenn sien
klein ist, kann eine einfachere lineare Version schneller sein. Hier sind Implementierungen von beiden:(Ich bin gegangen
x
und der Rückgabewert verdoppelt sich, weil das Ergebnis vonpow(double x, unsigned n)
ungefähr so oft wie möglich in ein Doppel passtpow(double, double)
.)(Ja,
pown
ist rekursiv, aber das Brechen des Stapels ist absolut unmöglich, da die maximale Stapelgröße ungefähr gleich istlog_2(n)
undn
eine Ganzzahl ist. Wennn
es sich um eine 64-Bit-Ganzzahl handelt, erhalten Sie eine maximale Stapelgröße von etwa 64. Keine Hardware ist so extrem Speicherbeschränkungen, mit Ausnahme einiger zwielichtiger PICs mit Hardware-Stacks, die nur 3 bis 8 Funktionsaufrufe tief gehen.)Was die Leistung angeht, werden Sie überrascht sein, wozu eine Gartensorte
pow(double, double)
in der Lage ist. Ich habe hundert Millionen Iterationen auf meinem 5 Jahre alten IBM Thinkpad getestetx
, die der Iterationsnummer undn
10 entsprechen. In diesem Szenario habe ichpown_l
gewonnen. glibcpow()
dauerte 12,0 Benutzersekunden,pown
7,4 Benutzersekunden undpown_l
nur 6,5 Benutzersekunden. Das ist also nicht allzu überraschend. Wir haben das mehr oder weniger erwartet.Dann lasse
x
ich konstant sein (ich setze es auf 2,5) und haben
hundert Millionen Mal von 0 auf 19 geschleift . Diesmalpow
gewann glibc ganz unerwartet und durch einen Erdrutsch! Es dauerte nur 2,0 Sekunden. Ichpown
brauchte 9,6 Sekunden undpown_l
12,2 Sekunden. Was ist hier passiert? Ich habe einen weiteren Test gemacht, um das herauszufinden.Ich habe das Gleiche wie oben gemacht, nur mit
x
einer Million. Diesmalpown
gewann bei 9,6s.pown_l
dauerte 12,2 s und glibc pow dauerte 16,3 s. Jetzt ist es klar! glibcpow
schneidet besser ab als die drei, wennx
es niedrig ist, aber am schlechtesten, wennx
es hoch ist. Wennx
hoch ist, istpown_l
die beste Leistung, wennn
es niedrig ist, undpown
die beste Leistung, wennx
es hoch ist.Hier sind drei verschiedene Algorithmen, von denen jeder unter den richtigen Umständen eine bessere Leistung als die anderen erzielen kann. Also, letztlich, die am ehesten zu verwenden , hängt davon ab , wie Sie planen , über die Verwendung
pow
, aber die richtige Version verwenden ist es wert, und ist schön , alle Versionen haben. Mit einer Funktion wie der folgenden können Sie sogar die Auswahl des Algorithmus automatisieren:Solange
x_expected
undn_expected
zum Zeitpunkt der Kompilierung Konstanten festgelegt werden, entfernt ein Optimierungs-Compiler, der es wert ist, automatisch den gesamtenpown_auto
Funktionsaufruf und ersetzt ihn durch die entsprechende Auswahl der drei Algorithmen. (Wenn Sie nun tatsächlich versuchen wollen, dies zu verwenden , müssen Sie wahrscheinlich ein wenig damit spielen, da ich nicht genau versucht habe , das zu kompilieren, was ich oben geschrieben habe .;))Auf der anderen Seite
pow
funktioniert glibc und glibc ist bereits groß genug. Der C-Standard soll portabel sein, auch für verschiedene eingebettete Geräte (tatsächlich sind sich eingebettete Entwickler überall im Allgemeinen einig, dass glibc bereits zu groß für sie ist), und er kann nicht portabel sein, wenn für jede einfache mathematische Funktion jede enthalten sein muss alternativer Algorithmus, könnte von nutzen sein. Deshalb ist es nicht im C-Standard.Fußnote: Beim Testen der
-s -O2
Zeitleistung habe ich meinen Funktionen relativ großzügige Optimierungsflags ( ) gegeben, die wahrscheinlich mit dem vergleichbar sind, wenn nicht sogar schlechter als das, was wahrscheinlich zum Kompilieren von glibc auf meinem System (archlinux) verwendet wurde. Die Ergebnisse sind also wahrscheinlich Messe. Für einen strengeren Test müsste ich glibc selbst kompilieren und ich habe wirklich keine Lust dazu. Früher habe ich Gentoo verwendet, daher erinnere ich mich, wie lange es dauert, auch wenn die Aufgabe automatisiert ist . Die Ergebnisse sind für mich schlüssig (oder eher nicht schlüssig) genug. Sie können dies natürlich gerne selbst tun.Bonusrunde: Eine Spezialisierung
pow(x, n)
auf alle Ganzzahlen ist von entscheidender Bedeutung, wenn eine genaue Ganzzahlausgabe erforderlich ist. Ziehen Sie in Betracht, Speicher für ein N-dimensionales Array mit p ^ N Elementen zuzuweisen. Wenn p ^ N auch nur um eins ausgeschaltet wird, tritt möglicherweise ein zufällig auftretender Segfault auf.quelle
n
. godbolt.org/z/L9Kb98 . gcc und clang können Ihre rekursive Definition nicht in eine einfache Schleife optimieren und verzweigen tatsächlich für jedes Bit vonn
. (Dennpown_iter(double,unsigned)
sie verzweigen immer noch, aber eine verzweigungslose SSE2- oder SSE4.1-Implementierung sollte in x86 asm oder mit C-Intrinsics möglich sein. Aber auch das ist besser als Rekursion)Ein Grund dafür, dass C ++ keine zusätzlichen Überladungen aufweist, ist die Kompatibilität mit C.
C ++ 98 hat Funktionen wie
double pow(double, int)
, aber diese wurden in C ++ 11 mit dem Argument entfernt, dass C99 sie nicht enthielt.http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3286.html#550
Ein etwas genaueres Ergebnis zu erzielen bedeutet auch, ein etwas anderes Ergebnis zu erzielen.
quelle
Die Welt entwickelt sich ständig weiter, ebenso wie die Programmiersprachen. Der vierte Teil der C-Dezimalstelle TR ¹ fügt einige weitere Funktionen hinzu
<math.h>
. Für diese Frage können zwei Familien dieser Funktionen von Interesse sein:pown
Funktionen, die eine Gleitkommazahl und einenintmax_t
Exponenten annehmen.powr
Funktionen, die zwei Gleitkommazahlen (x
undy
) verwenden und mit der Formelx
zur Potenzy
berechnenexp(y*log(x))
.Es scheint, dass die Standard-Leute diese Funktionen letztendlich als nützlich genug erachteten, um in die Standardbibliothek integriert zu werden. Das Rationale ist jedoch, dass diese Funktionen vom ISO / IEC / IEEE 60559: 2011- Standard für binäre und dezimale Gleitkommazahlen empfohlen werden . Ich kann nicht sicher sagen, welcher "Standard" zum Zeitpunkt von C89 befolgt wurde, aber die zukünftigen Entwicklungen von
<math.h>
werden wahrscheinlich stark von den zukünftigen Entwicklungen des ISO / IEC / IEEE 60559- Standards beeinflusst.Beachten Sie, dass der vierte Teil der Dezimal-TR nicht in C2x (der nächsten größeren C-Revision) enthalten ist und wahrscheinlich später als optionale Funktion enthalten sein wird. Ich hatte keine Absicht, diesen Teil der TR in eine zukünftige C ++ - Revision aufzunehmen.
¹ Sie können einige Arbeit-in-progress finden Dokumentation hier .
quelle
pown
mit einem Exponenten, der größer ist alsLONG_MAX
jemals, einen anderen Wert ergeben sollte als die VerwendungLONG_MAX
, oder bei dem ein kleinerer Wert als jemals einen anderen WertLONG_MIN
ergeben sollte alsLONG_MIN
? Ich frage mich, welchen Nutzen die Verwendungintmax_t
für einen Exponenten hat.Vielleicht, weil die ALU des Prozessors eine solche Funktion für ganze Zahlen nicht implementiert hat, aber es gibt eine solche FPU-Anweisung (wie Stephen betont, handelt es sich tatsächlich um ein Paar). Es war also tatsächlich schneller, in Double umzuwandeln, pow mit Doubles aufzurufen, dann auf Überlauf zu testen und zurückzusetzen, als es mit ganzzahliger Arithmetik zu implementieren.
(Zum einen reduzieren Logarithmen die Potenzen auf Multiplikation, aber Logarithmen von ganzen Zahlen verlieren bei den meisten Eingaben viel Genauigkeit.)
Stephen hat Recht, dass dies auf modernen Prozessoren nicht mehr der Fall ist, aber der C-Standard, als die mathematischen Funktionen ausgewählt wurden (C ++ hat nur die C-Funktionen verwendet), ist jetzt was, 20 Jahre alt?
quelle
pow
. x86 verfügt über einey log2 x
Anweisung (fyl2x
), die als erster Teil einerpow
Funktion verwendet werden kann. Eine aufpow
diese Weise geschriebene Funktion benötigt jedoch Hunderte von Zyklen, um auf der aktuellen Hardware ausgeführt zu werden. Eine gut geschriebene Potenzierungsroutine für ganze Zahlen ist um ein Vielfaches schneller.Hier ist eine wirklich einfache O (log (n)) - Implementierung von pow (), die für alle numerischen Typen, einschließlich Ganzzahlen , funktioniert :
Es ist besser als die O (log (n)) - Implementierung von enigmaticPhysicist, da keine Rekursion verwendet wird.
Es ist auch fast immer schneller als seine lineare Implementierung (solange p> ~ 3), weil:
quelle
Tatsächlich tut es das auch.
Seit C ++ 11 gibt es eine Vorlagenimplementierung von
pow(int, int)
--- und noch allgemeineren Fällen, siehe (7) unter http://en.cppreference.com/w/cpp/numeric/math/powEDIT: Puristen können argumentieren, dass dies nicht korrekt ist, da tatsächlich "geförderte" Typisierung verwendet wird. Auf die eine oder andere Weise erhält man ein korrektes
int
Ergebnis oder einen Fehler bei denint
Parametern.quelle
pow ( Arithmetic1 base, Arithmetic2 exp )
indouble
oder umgewandelt,long double
wenn Sie die Beschreibung gelesen haben: "7) Eine Reihe von Überladungen oder eine Funktionsvorlage für alle Kombinationen von Argumenten vom arithmetischen Typ, die nicht unter 1-3 fallen.) Wenn ein Argument vorliegt hat einen integralen Typ und wird in double umgewandelt. Wenn ein Argument long double ist, ist der gesponserte Rückgabetyp auch long double, andernfalls ist der Rückgabetyp immer double. "pow(1.5f, 3)
=1072693280
aberpow(1.5f, float(3))
=3.375
int pow(int, int)
, aber C ++ 11 bietet nurdouble pow(int, int)
. Siehe die Erklärung von @phuclv.Ein sehr einfacher Grund:
Alles in der STL-Bibliothek basiert auf den genauesten und robustesten Dingen, die man sich vorstellen kann. Sicher, das int würde auf eine Null zurückkehren (von 1/25), aber dies wäre eine ungenaue Antwort.
Ich stimme zu, es ist in einigen Fällen komisch.
quelle