2009-02-05 3 views
8

Ich versuche, eine zeilenlose Funktion zu schreiben, um die MAX oder MIN von zwei Ganzzahlen zurückgeben, ohne auf if (oder? :). the usual technique ich mit dieser leicht genug für ein gegebenes Wort Größe tun können:Templazed Zweiglos Int Max/Min-Funktion

inline int32 imax(int32 a, int32 b) 
{ 
    // signed for arithmetic shift 
    int32 mask = a - b; 
    // mask < 0 means MSB is 1. 
    return a + ((b - a) & (mask >> 31)); 
} 

Wenn man nun annimmt arguendo, dass ich wirklich die Art von Anwendung auf die Art von in-Order-Prozessor, wo dies notwendig ist, meine Frage schreibe ist Gibt es eine Möglichkeit, C++ - Vorlagen zu verwenden, um dies auf alle Größen von int zu verallgemeinern?

Der >> 31 Schritt funktioniert natürlich nur für Int32s, und während ich Überladungen auf die Funktion für int8, int16 und int64 kopieren konnte, scheint es, dass ich stattdessen eine Template-Funktion verwenden sollte. Aber wie bekomme ich die Größe eines Template-Arguments in Bits?

Gibt es einen besseren Weg, dies zu tun? Kann ich die Unterschrift der Maske T erzwingen? Wenn T nicht vorzeichenbehaftet ist, wird der Maskenverschiebungsschritt nicht funktionieren (weil es eine logische statt einer arithmetischen Verschiebung ist).

template< typename T > 
inline T imax(T a, T b) 
{ 
    // how can I force this T to be signed? 
    T mask = a - b; 
    // I hope the compiler turns the math below into an immediate constant! 
    mask = mask >> ((sizeof(T) * 8) - 1); 
    return a + ((b - a) & mask); 
} 

Und die oben getan zu haben, kann ich verhindern, dass es aber einen Integer-Typ für alles verwendet werden (zB keine Schwimmer oder Klassen)?

+0

Die meisten modernen Maschinen haben Konditional mov Anweisungen, die ihnen ermöglichen, ohne Äste min/max zu tun (z. B. cmp a, b/movlt a, b). Dies wäre schneller als der Code, den Sie generieren möchten, und die Compiler kennen sie. Bist du sicher, dass dein Compiler das nicht schon für dich tut? –

+1

@IraBaxter Absolut sicher; Ich schaue immer auf seine Montageausgabe. Auch der Zielprozessor (ein PowerPC-Derivat) hat definitiv kein cmov. – Crashworks

+1

Egal welchen Code Sie schreiben, er wird nur als C++ - Quelle abzweigfrei sein. Der Compiler kann bedingte Sprünge (dh Verzweigungen) erzeugen, ohne if/else /? /: Zu schreiben, und umgekehrt kann er optimierte verzweigungsfreie Instruktionen von der if/else-Quelle erzeugen. – galinette

Antwort

8

Im Allgemeinen sieht gut aus, aber für 100% Portabilität, ersetzen Sie diese 8 mit CHAR_BIT (oder numeric_limits :: max()), da es nicht garantiert ist, dass Zeichen 8-Bit sind.

Jeder gute Compiler ist schlau genug, um alle mathematischen Konstanten zur Kompilierzeit zusammenzuführen.

Sie können erzwingen, dass es signiert wird, indem Sie eine Typeigenschaften-Bibliothek verwenden. die in der Regel so etwas wie (vorausgesetzt, Ihre numeric_traits Bibliothek aufgerufen wird numeric_traits) aussehen:

typename numeric_traits<T>::signed_type x; 

Ein Beispiel eines manuell gerollt numeric_traits Header könnte wie folgt aussehen: http://rafb.net/p/Re7kq478.html (es gibt viel Platz für Ergänzungen, aber Sie das bekommen Idee).

oder besser noch, verwenden boost:

typename boost::make_signed<T>::type x; 

EDIT: IIRC unterzeichnete Rechtsverschiebungen nicht haben Arithmetik sein. Es ist üblich, und sicherlich bei jedem Compiler, den ich verwendet habe. Aber ich glaube, dass der Standard es dem Compiler überlässt, ob Rechtsverschiebungen arithmetisch sind oder nicht. In meiner Kopie des Entwurfs des Standard wird die folgende geschrieben:

Der Wert von E1 >> E2 E1 E2 Bitpositionen rightshifted. Wenn E1 hat einen unsigned Typen oder wenn E1 einen signierten Typen und einen nicht-negativen Wert hat, der Wert des Ergebnisses ist der integrale Bestandteil des Quotienten aus E1 geteilt durch die Menge 2 bis die Kraft E2 erhöht. Wenn E1 einen vorzeichenbehafteten Typ und einen negativen Wert hat, ist der resultierende Wert die Implementierung.

Aber wie ich schon sagte, es wird mit jedem Compiler funktionieren, den ich gesehen habe :-p.

+4

Mein Verstand schaudert, um sich vorzustellen, was im Herzen des Compiler-Implementators liegen könnte, der sich dafür entscheidet, das Zeichen nicht zu bewahren. – Crashworks

+0

+1 für die Erwähnung von CHAR_BIT und die Implementationsdefinition von signierten Rechtsverschiebungen (beide Nachrichten für mich), aber beachten Sie, dass automatische Vorlagentyp Abzug kann nicht abgeleitet werden T für einen Typ wie "numeric_traits :: signed_type" - Sie benötigen stattdessen stattdessen enable_if verwenden. (Wie von grecreteawk erwähnt.) –

+0

@j_random_hacker: Ich sehe nicht, warum es nicht funktionieren würde, wenn Sie es taten: int x = imax (5, 4); Keine Notwendigkeit für enable_if –

2

Hier ist ein weiterer Ansatz für max. Und min. Was ist nett daran ist, dass es keine Bittricks verwendet und Sie nichts über den Typ wissen müssen.

template <typename T> 
inline T imax (T a, T b) 
{ 
    return (a > b) * a + (a <= b) * b; 
} 

template <typename T> 
inline T imin (T a, T b) 
{ 
    return (a > b) * b + (a <= b) * a; 
} 
+2

Unglücklicherweise auf dem PowerPC ist Integer-Multiplikation eine Mikrocodierte Operation, die die Pipeline tot stoppt, und ist sogar langsamer als eine falsch vorhergesagte Verzweigung. – Crashworks

+2

@Crashworks Ich habe das in irgendeinem Programm auf x86_64 versucht, und es war tatsächlich langsamer als der übliche Zweigansatz. –

+0

Was ist mit '(- (a <= b) & a) | (- (b <= a) & b) "? –