Verwendung von Min- und Max-Funktionen in C ++

76

Von C ++ sind minund maxbevorzugt über fminund fmax? Bieten sie für den Vergleich zweier Ganzzahlen grundsätzlich dieselbe Funktionalität?

Verwenden Sie in der Regel eine dieser Funktionen oder schreiben Sie lieber eine eigene (möglicherweise, um die Effizienz, Portabilität, Flexibilität usw. zu verbessern)?

Anmerkungen:

  1. Die C ++ Standard Template Library (STL) deklariert die minund max-Funktionen im Standard-C ++ - Algorithmus- Header.

  2. Der C-Standard (C99) bietet die fminund fmax-Funktion im Standard-C math.h- Header.

Danke im Voraus!

bporter
quelle

Antworten:

115

fminund fmaxsind speziell für die Verwendung mit Gleitkommazahlen (daher das "f"). Wenn Sie es für Ints verwenden, können je nach Compiler / Plattform Leistungs- oder Präzisionsverluste aufgrund von Konvertierung, Funktionsaufruf-Overhead usw. auftreten.

std::minund std::maxsind Vorlagenfunktionen (im Header definiert <algorithm>), die für jeden Typ mit einem <Operator kleiner als ( ) funktionieren, sodass sie für jeden Datentyp ausgeführt werden können, der einen solchen Vergleich ermöglicht. Sie können auch Ihre eigene Vergleichsfunktion bereitstellen, wenn diese nicht funktionieren soll <.

Dies ist sicherer, da Sie Argumente explizit konvertieren müssen, damit sie übereinstimmen, wenn sie unterschiedliche Typen haben. Mit dem Compiler können Sie beispielsweise nicht versehentlich ein 64-Bit-Int in ein 64-Bit-Float konvertieren. Allein dieser Grund sollte die Vorlagen zu Ihrer Standardauswahl machen. (Dank an Matthieu M & bk1e)

Auch wenn mit Schwimmern die Vorlage verwendet kann in der Leistung gewinnen. Ein Compiler hat immer die Möglichkeit, Aufrufe von Vorlagenfunktionen einzubinden, da der Quellcode Teil der Kompilierungseinheit ist. Manchmal ist es andererseits unmöglich , einen Aufruf einer Bibliotheksfunktion inline zu setzen (gemeinsam genutzte Bibliotheken, fehlende Optimierung der Verbindungszeit usw.).

Zahnrad
quelle
9
Warnung: min und max können nur zwei Variablen des exakt gleichen Typs vergleichen ... Sie können also kein int und kein double mit ihnen vergleichen :(
Matthieu M.
5
True - max (1, 2.0) funktioniert nicht, es muss so etwas wie max <double> (1, 2.0) oder max (double (1), 2.0) sein.
David Thornley
52
Welches ist eine gute Sache ™ IMO :)
Zahnrad
2
Dies ist eine große Annahme, dass für die Konvertierung Kosten anfallen. Auf einigen Systemen besteht der einzige Unterschied darin, dass die Werte vor dem Vergleich in einen FPU-Regester und nicht in ein normales Register geladen werden.
Martin York
1
Gibt es Plattformen mit 64-Bit-Ints (ILP64) und 64-Bit-Doubles? Auf diesen Plattformen würde die Konvertierung von int zu double zu einem Genauigkeitsverlust bei extrem positiven / negativen Ints führen.
bk1e
38

Es gibt einen wichtigen Unterschied zwischen std::min, std::maxund fminund fmax.

std::min(-0.0,0.0) = -0.0
std::max(-0.0,0.0) = -0.0

wohingegen

fmin(-0.0, 0.0) = -0.0
fmax(-0.0, 0.0) =  0.0

Ist std::minalso kein 1: 1-Ersatz für fmin. Die Funktionen std::minund std::maxsind nicht kommutativ. Um das gleiche Ergebnis mit Doppel mit fminund fmaxzu erhalten, sollte man die Argumente austauschen

fmin(-0.0, 0.0) = std::min(-0.0,  0.0)
fmax(-0.0, 0.0) = std::max( 0.0, -0.0)

Soweit ich jedoch sagen kann, sind alle diese Funktionen in diesem Fall ohnehin definiert. Um 100% sicher zu sein, müssen Sie testen, wie sie implementiert sind.


Es gibt noch einen weiteren wichtigen Unterschied. Für x ! = NaN:

std::max(Nan,x) = NaN
std::max(x,NaN) = x
std::min(Nan,x) = NaN
std::min(x,NaN) = x

wohingegen

fmax(Nan,x) = x
fmax(x,NaN) = x
fmin(Nan,x) = x
fmin(x,NaN) = x

fmax kann mit dem folgenden Code emuliert werden

double myfmax(double x, double y)
{
   // z > nan for z != nan is required by C the standard
   int xnan = isnan(x), ynan = isnan(y);
   if(xnan || ynan) {
        if(xnan && !ynan) return y;
        if(!xnan && ynan) return x;
        return x;
   }
   // +0 > -0 is preferred by C the standard 
   if(x==0 && y==0) {
       int xs = signbit(x), ys = signbit(y);
       if(xs && !ys) return y;
       if(!xs && ys) return x;
       return x;
   }
   return std::max(x,y);
}

Dies zeigt, dass dies std::maxeine Teilmenge von ist fmax.

Ein Blick auf die Assembly zeigt, dass Clang integrierten Code für fmaxund verwendet, fminwährend GCC sie aus einer Mathematikbibliothek aufruft. Die Baugruppe zum Klirren fmaxmit -O3ist

movapd  xmm2, xmm0
cmpunordsd      xmm2, xmm2
movapd  xmm3, xmm2
andpd   xmm3, xmm1
maxsd   xmm1, xmm0
andnpd  xmm2, xmm1
orpd    xmm2, xmm3
movapd  xmm0, xmm2

während dafür ist std::max(double, double)es einfach

maxsd   xmm0, xmm1

Für GCC und Clang wird die Verwendung -Ofast fmaxjedoch einfach

maxsd   xmm0, xmm1

Dies zeigt also noch einmal, dass dies std::maxeine Teilmenge von ist fmaxund dass, wenn Sie ein lockereres Gleitkommamodell verwenden, das dann keine nanNull hat oder vorzeichenbehaftet ist fmaxund std::maxdasselbe ist. Das gleiche Argument gilt offensichtlich für fminund std::min.

Z Boson
quelle
Die Anweisungen für maxsd / minsd entsprechen fmax, fmin in Bezug auf das Löschen von Nan. Bei zwei Nullen mit unterschiedlichen Vorzeichen wählen sie jedoch nicht das Max- oder Min-Zeichen aus. Ich kann jedoch keine Dokumentation finden, die besagt, dass fmax, fmin so definiert sind, dass sie Nullen auf diese Weise verarbeiten. +0 und -0 werden im Allgemeinen als gleichwertig angesehen, sofern kein spezifisches Verhalten definiert ist. Ich glaube, es gibt keinen Grund, MAXSD nicht für fmax zu verwenden, unabhängig von -Ofast. Außerdem denke ich, dass std :: max <double> möglicherweise fmax zugeordnet ist oder nicht, je nachdem, welche Header Sie eingefügt haben (wodurch sich die Behandlung von Nan ändert).
Greggo
1
@greggo, der C-Standard besagt: "Idealerweise reagiert fmax empfindlich auf das Vorzeichen Null, z. B. würde fmax (-0,0, +0,0) +0 zurückgeben. Die Implementierung in Software ist jedoch möglicherweise unpraktisch." Es ist also keine Voraussetzung für fmin / fmax, sondern eine Präferenz. Als ich diese Funktionen getestet habe, haben sie das Bevorzugte getan.
Z Boson
@greggo, das habe ich in meiner Antwort gesagt. Schauen Sie sich die Kommentare im Code "// z> nan für z! = Nan wird von C als Standard benötigt" und "// +0> -0 wird von C als Standard bevorzugt" an.
Z Boson
@greggo, ich habe Ihre Behauptung getestet, dass maxsd / minsd nan fallen lassen, und das ist nicht das, was ich bei coliru.stacked-crooked.com/a/ca78268b6b9f5c88 beobachte . Die Operatoren pendeln nicht wie bei einer vorzeichenbehafteten Null.
Z Boson
@greggo, hier ist ein besseres Beispiel, das ich verwendet habe und _mm_max_sddas zeigt, dass maxsd weder nan fallen lässt noch pendelt. coliru.stacked-crooked.com/a/768f6d831e79587f
Z Boson
16

Sie vermissen den gesamten Punkt von fmin und fmax. Es wurde in C99 aufgenommen, damit moderne CPUs ihre nativen (gelesenen SSE) Anweisungen für Gleitkomma-Min und Max verwenden und einen Test und eine Verzweigung (und damit eine möglicherweise falsch vorhergesagte Verzweigung) vermeiden können. Ich habe Code neu geschrieben, der std :: min und std :: max verwendet hat, um stattdessen SSE-Intrinsics für min und max in inneren Schleifen zu verwenden, und die Beschleunigung war signifikant.

J Ridges
quelle
1
Wie groß war die Beschleunigung? Warum kann der C ++ - Compiler nicht erkennen, wenn Sie std :: min <double> verwenden?
Janus Troelsen
5
Vielleicht hatte er beim Testen keine Optimierung aktiviert, oder der Compiler versuchte, eine Binärdatei zu kompilieren, die "überall" ausgeführt werden konnte und daher nicht wusste, dass sie SSE verwenden konnte. Ich vermute, dass mit gcc die Unterschiede verschwinden würden, wenn Sie die Flaggen passieren würden-O3 -march=native
David Stone
3
Der eigentliche Grund, warum es in C enthalten war, war, dass C keine Vorlagen oder Funktionsüberladungen hat, sodass sie eine Funktion mit einem anderen Namen als nur max für Gleitkommatypen erstellen.
David Stone
habe gerade versucht, dies auf g ++ 4.8: fmax, std::max<double> und sogar (a>b)?a:balle sind einer einzelnen maxsd-Anweisung auf -O1 zugeordnet. (So ​​erhalten Sie eine andere Behandlung von NaNs als bei -O0 ...)
Greggo
6

std :: min und std :: max sind Vorlagen. Sie können also für eine Vielzahl von Typen verwendet werden, die weniger als den Bediener bieten, einschließlich Floats, Doubles und Long Doubles. Wenn Sie also generischen C ++ - Code schreiben möchten, gehen Sie wie folgt vor:

template<typename T>
T const& max3(T const& a, T const& b, T const& c)
{
   using std::max;
   return max(max(a,b),c); // non-qualified max allows ADL
}

Was die Leistung betrifft, denke ich nicht fminund fmaxunterscheide mich von ihren C ++ - Gegenstücken.

sellibitze
quelle
1
Was ist ADL und warum wollen wir es hier?
Rob Kennedy
1
ADL = argumentabhängige Suche. In diesem Fall ist dies wahrscheinlich nicht erforderlich, da jeder benutzerdefinierte Typ, der über eine eigene Max-Funktion verfügt, wahrscheinlich auch einen speziellen Operator für weniger als bietet. Es ist nur eine Gewohnheit von mir, dass ich Code wie diesen schreibe - hauptsächlich mit swapund einigen numerischen Funktionen wie abs. Sie möchten die speziellen Swap- und Abs-Funktionen eines Typs anstelle der generischen verwenden, falls die speziellen vorhanden sind. Ich schlage vor, Herb Sutters Artikel über "Namespaces und das Interface-Prinzip" zu lesen
sellibitze
6

Wenn Ihre Implementierung einen 64-Bit-Integer-Typ bereitstellt, erhalten Sie möglicherweise eine andere (falsche) Antwort, wenn Sie fmin oder fmax verwenden. Ihre 64-Bit-Ganzzahlen werden in Doubles konvertiert, die (zumindest normalerweise) einen Signifikanten haben, der kleiner als 64-Bit ist. Wenn Sie eine solche Zahl in ein Doppel umwandeln, können / werden einige der niedrigstwertigen Bits vollständig verloren gehen.

Dies bedeutet, dass zwei wirklich unterschiedliche Zahlen bei der Konvertierung in double gleich sein können - und das Ergebnis ist diese falsche Zahl, die nicht unbedingt einer der ursprünglichen Eingaben entspricht.

Jerry Sarg
quelle
4

Ich würde die C ++ min / max-Funktionen bevorzugen, wenn Sie C ++ verwenden, da sie typspezifisch sind. fmin / fmax erzwingt, dass alles in / von Gleitkomma konvertiert wird.

Außerdem funktionieren die C ++ min / max-Funktionen mit benutzerdefinierten Typen, solange Sie den Operator <für diese Typen definiert haben.

HTH

Eric Melski
quelle
2

Wie Sie selbst bemerkt haben fminund fmaxin C99 eingeführt wurden. Standard C ++ Bibliothek hat fminund fmaxfunktioniert nicht. Bis die C99-Standardbibliothek (falls vorhanden) in C ++ integriert wird, werden die Anwendungsbereiche dieser Funktionen sauber getrennt. Es gibt keine Situation, in der Sie einander vorziehen müssen.

Sie verwenden nur templated std::min/ std::maxin C ++ und verwenden alles, was in C verfügbar ist.

Ameise
quelle
2

Verwenden Sie, wie Richard Corden betonte, die im Standard-Namespace definierten C ++ - Funktionen min und max. Sie bieten Typensicherheit und helfen, den Vergleich gemischter Typen (dh Gleitkomma mit Ganzzahl) zu vermeiden, was manchmal unerwünscht sein kann.

Wenn Sie feststellen, dass die von Ihnen verwendete C ++ - Bibliothek min / max ebenfalls als Makros definiert, kann dies zu Konflikten führen. Sie können dann verhindern, dass unerwünschte Makrosubstitutionen die min / max-Funktionen auf diese Weise aufrufen (beachten Sie zusätzliche Klammern):

(std::min)(x, y)
(std::max)(x, y)

Denken Sie daran, dass dadurch die argumentabhängige Suche (ADL, auch Koenig-Suche genannt) effektiv deaktiviert wird , falls Sie sich auf ADL verlassen möchten.

Mloskot
quelle
1

fmin und fmax gelten nur für Gleitkomma- und Doppelvariablen.

min und max sind Vorlagenfunktionen, die den Vergleich beliebiger Typen bei einem binären Prädikat ermöglichen. Sie können auch mit anderen Algorithmen verwendet werden, um komplexe Funktionen bereitzustellen.

Marcin
quelle
1

Verwenden Sie std::minund std::max.

Wenn die anderen Versionen schneller sind, kann Ihre Implementierung Überladungen für diese hinzufügen, und Sie profitieren von Leistung und Portabilität:

template <typename T>
T min (T, T) {
  // ... default
}

inline float min (float f1, float f2) {
 return fmin( f1, f2);
}    
Richard Corden
quelle
1

Könnte eine C ++ - Implementierung, die auf Prozessoren mit SSE-Anweisungen abzielt , nicht Spezialisierungen von std :: min und std :: max für die Typen float , double und long double bereitstellen , die das Äquivalent von fminf , fmin bzw. fminl haben?

Die Spezialisierungen würden eine bessere Leistung für Gleitkommatypen bieten, während die allgemeine Vorlage Nicht-Gleitkommatypen verarbeiten würde, ohne zu versuchen, Gleitkommatypen in Gleitkommatypen zu zwingen, wie dies bei fmin s und fmax es der Fall wäre.

user3405743
quelle
Intel c ++ bietet für std :: min eine bessere Leistung als fmin. In gcc erfordert eine gute Leistung von fmin eine Einstellung nur für endliche Mathematik, die es für nicht endliche Operanden unterbricht.
Tim18
0

Ich benutze immer die Min- und Max-Makros für Ints. Ich bin mir nicht sicher, warum jemand fmin oder fmax für ganzzahlige Werte verwenden würde.

Das große Problem bei Min und Max ist, dass sie keine Funktionen sind, auch wenn sie so aussehen. Wenn Sie etwas tun wie:

min (10, BigExpensiveFunctionCall())

Dieser Funktionsaufruf kann abhängig von der Implementierung des Makros zweimal aufgerufen werden. Daher ist es in meiner Organisation die beste Vorgehensweise, niemals min oder max mit Dingen aufzurufen, die kein Literal oder keine Variable sind.

Popester
quelle
7
min und max werden in C häufig als Makros implementiert, dies ist jedoch C ++, wo sie als Vorlagen implementiert sind. Viel, viel besser.
David Thornley
4
Wenn Sie #include <windows.h>, erhalten minund maxals Makros definiert. Dies steht in Konflikt mit std::minund std::max, daher müssen Sie Ihre Quellen mit kompilieren #define NOMINMAX, um die ersteren auszuschließen.
Steve Guidi
3
Es wäre schön gewesen, wenn Microsoft einen #ifdef _WINDOWS #undef minin den <algorithm>Header eingefügt hätte.
Spart
2
@MSalters: Gute Idee, dies liegt jedoch nicht in der Verantwortung der Standardbibliothek. Sie hätten den Namespace nicht mit solchen gebräuchlichen Namen verschmutzen sollen.
the_drow
Es gibt ein seltsames Problem std::min: Es akzeptiert tatsächlich zwei konstante Referenzen und gibt eine davon zurück. Normalerweise wird das vom Compiler gefaltet. Aber ich hatte einmal eine std::min( x, constval), wo constvalwie static const int constval=10;in der Klasse definiert wurde. Und ich habe einen Linkfehler bekommen : undefined MyClass::constval. Da muss nun die Konstante existieren, da ein Verweis darauf genommen wird. Kann mitstd::min( x, constval+0)
greggo
0

fminund fmax, von fminlund fmaxlkönnte beim Vergleich von vorzeichenbehafteten und vorzeichenlosen Ganzzahlen bevorzugt werden - Sie können die Tatsache nutzen, dass der gesamte Bereich der vorzeichenbehafteten und vorzeichenlosen Zahlen und Sie müssen sich keine Gedanken über ganzzahlige Bereiche und Werbeaktionen machen.

unsigned int x = 4000000000;
int y = -1;

int z = min(x, y);
z = (int)fmin(x, y);
Finsternis
quelle
Warum gibt es keine Spezialisierungen, die diese Fälle behandeln?
the_drow