it-swarm.com.de

Der schnellste Weg, ein positives Modulo in C/C++ zu erhalten

In meinen inneren Schleifen muss ich oft ein Array in "Wrap-around" Weise indizieren, so dass, wenn die Arraygröße 100 ist und mein Code nach Element -2 fragt, diesem Element Element 98 zugewiesen werden sollte Als Python kann man dies einfach mit my_array[index % array_size] tun, aber aus irgendeinem Grund rundet sich die Ganzzahlarithmetik von C (normalerweise) gegen Null, statt sie konsequent abzurunden. Folglich gibt der Modulo-Operator ein negatives Ergebnis zurück, wenn ein negatives erstes Argument angegeben wird.

Oft weiß ich, dass index nicht weniger als -array_size ist, und in diesen Fällen mache ich einfach my_array[(index + array_size) % array_size]. Manchmal kann dies jedoch nicht garantiert werden, und für diese Fälle würde ich gerne wissen, wie Sie eine immer positive Modulo-Funktion am schnellsten implementieren können. Es gibt mehrere "clevere" Möglichkeiten, dies zu tun, ohne zu verzweigen, wie z

inline int positive_modulo(int i, int n) {
    return (n + (i % n)) % n
}

oder 

inline int positive_modulo(int i, int n) {
    return (i % n) + (n * (i < 0))
}

Natürlich kann ich diese Angaben machen, um herauszufinden, welcher der schnellste auf meinem System ist, aber ich muss mir keine Sorgen machen, dass ich vielleicht einen besseren verpasst habe oder dass das, was auf meinem Rechner schnell ist, auf einem anderen langsam sein könnte.

Gibt es eine Standardmethode, um dies zu tun, oder einen cleveren Trick, den ich vermisst habe, der wahrscheinlich die schnellste Möglichkeit ist?

Ich weiß auch, dass es wahrscheinlich ein Wunschdenken ist, aber wenn es einen Weg gibt, der automatisch vektorisiert werden kann, wäre das erstaunlich.

49
Nathaniel

Die Standardmethode, die ich gelernt habe, ist

inline int positive_modulo(int i, int n) {
    return (i % n + n) % n;
}

Diese Funktion ist im Wesentlichen Ihre erste Variante ohne die abs (was tatsächlich dazu führt, dass sie das falsche Ergebnis liefert). Es würde mich nicht wundern, wenn ein optimierender Compiler dieses Muster erkennen und in Maschinencode kompilieren könnte, der ein "unsigned modulo" berechnet.

Bearbeiten:

Weiter zur zweiten Variante: Zunächst enthält es auch einen Fehler - der n < 0 sollte i < 0 sein.

Diese Variante sieht möglicherweise nicht so aus, als würde sie verzweigen, aber bei vielen Architekturen wird der i < 0 in einen bedingten Sprung kompiliert. In jedem Fall ist es mindestens genauso schnell, (n * (i < 0)) durch i < 0? n: 0 zu ersetzen, wodurch die Multiplikation vermieden wird. Darüber hinaus ist es "sauberer", da der bool nicht als int interpretiert werden muss.

Welche der beiden Varianten schneller ist, hängt wahrscheinlich von der Compiler- und Prozessorarchitektur ab - mal die beiden Varianten und siehe. Ich glaube nicht, dass es einen schnelleren Weg als diese beiden Varianten gibt.

63
Martin B

Modulo eine Zweierpotenz, die folgenden Werke (vorausgesetzt, zwei Komplementendarstellungen)

return i & (n-1);
22
nneonneo

Eine Methode der alten Schule, um den optionalen Addend unter Verwendung der Weitergabe von Zweierkomplementzeichen zu erhalten:

int positive_mod(int i, int n)
{
    /* constexpr */ int shift = CHAR_BIT*sizeof i - 1;
    int m = i%n;
    return m+ (m>>shift & n);
}
6
jthill

Wenn Sie es sich leisten können, auf einen größeren Typ umzusteigen (und Ihr Modulo mit dem größeren Typ ausführen), führt dieser Code ein einzelnes Modulo aus und kein, wenn

int32_t positive_modulo(int32_t number, int32_t modulo) {
    return (number + ((int64_t)modulo << 32)) % modulo;
}
1
user15006

Sie können auch array[(i+array_size*N) % array_size] tun, wobei N eine ganze Zahl ist, die ein positives Argument garantiert, aber klein genug ist, um nicht überzulaufen.

Wenn die array_size konstant ist, gibt es Techniken zur Berechnung des Moduls ohne Division. Neben der Zweierpotenz kann man eine gewichtete Summe von Bitgruppen multiplizieren mit 2 ^ i% n berechnen, wobei i das niedrigstwertige Bit in jeder Gruppe ist:

z.B. 32-Bit-Ganzzahl 0xaabbccdd% 100 = dd + cc * [2] 56 + bb * [655] 36 + aa * [167772] 16 mit dem maximalen Bereich von (1 + 56 + 36 + 16) * 255 = 27795. Bei wiederholten Anwendungen und unterschiedlicher Unterteilung kann die Operation auf wenige bedingte Subtraktionen reduziert werden.

Zu den gängigen Praktiken gehört auch die Annäherung der Division mit einem Kehrwert von 2 ^ 32/n, die normalerweise einen relativ großen Bereich von Argumenten verarbeiten kann.

 i - ((i * 655)>>16)*100; // (gives 100*n % 100 == 100 requiring adjusting...)
1
Aki Suihkonen

Schnellster Weg, um ein positives Modulo in C/C++ zu erhalten

Das folgende schnell? - vielleicht nicht so schnell wie andere, ist aber einfach und funktionell für alle korrekt1 a,b - im Gegensatz zu anderen.

int modulo_Euclidean(int a, int b) {
  int m = a % b;
  if (m < 0) {
    // m += (b < 0) ? -b : b; // avoid this form: it is UB when b == INT_MIN
    m = (b < 0) ? m - b : m + b;
  }
  return m;
}

Verschiedene andere Antworten haben mod(a,b) Schwächen, insbesondere wenn b < 0.

Siehe Euklidische Division für Ideen zu b < 0


inline int positive_modulo(int i, int n) {
    return (i % n + n) % n;
}

Schlägt fehl, wenn i % n + n Überläuft (denke groß i, n) - Undefiniertes Verhalten.


return i & (n-1);

Verlässt sich auf n als Zweierpotenz. (Schön, dass die Antwort dies erwähnt.)


int positive_mod(int i, int n)
{
    /* constexpr */ int shift = CHAR_BIT*sizeof i - 1;
    int m = i%n;
    return m+ (m>>shift & n);
}

Scheitert oft, wenn n < 0. e, g, positive_mod(-2,-3) --> -5


int32_t positive_modulo(int32_t number, int32_t modulo) {
    return (number + ((int64_t)modulo << 32)) % modulo;
}

Obligatorisch mit 2 ganzzahligen Breiten. (Schön, dass die Antwort dies erwähnt.)
Scheitert mit modulo < 0. positive_modulo(2, -3) -> -1.


inline int positive_modulo(int i, int n) {
    int tmp = i % n;
    return tmp ? i >= 0 ? tmp : tmp + n : 0;
}

Scheitert oft, wenn n < 0. e, g, positive_modulo(-2,-3) --> -5


1 Ausnahmen: In C ist a%b Nicht definiert, wenn a/b Wie in a/0 Oder INT_MIN/-1 Überläuft.

0
chux

Ihr zweites Beispiel ist besser als das erste. Eine Multiplikation ist eine komplexere Operation als eine if/else -Operation.

inline int positive_modulo(int i, int n) {
    int tmp = i % n;
    return tmp ? i >= 0 ? tmp : tmp + n : 0;
}
0
SkYWAGz