8

Mit Blick auf x86 Assembly von einem Compiler erzeugt, bemerkte ich, dass (vorzeichenlose) Ganzzahl Divisionen manchmal als ganzzahlige Multiplikationen implementiert sind. Diese Optimierungen scheinen der FormFühren Sie Ganzzahldivision mit Multiplikation

value/n => (value * ((0xFFFFFFFF/n) + 1))/0x100000000 

Zum Beispiel zu folgen, eine Division durch 9 ausführen:

12345678/9 = (12345678 * 0x1C71C71D)/0x100000000 

Eine Division durch 3 verwenden würde Multiplikation mit 0x55555555 + 1, und so weiter.

Ausnutzen der Tatsache, dass die mul Anweisung den hohen Teil des Ergebnisses im edx Register speichert, kann das Endergebnis der Division mit einer einzigen Multiplikation mit einem magischen Wert erhalten werden. (Obwohl diese Optimierung manchmal in Verbindung mit einer bitweisen Verschiebung am Ende verwendet wird.)

Ich hätte gerne einen Einblick, wie das tatsächlich funktioniert. Wann ist dieser Ansatz gültig? Warum muss 1 zu unserer "magischen Zahl" hinzugefügt werden?

+2

Die Konstante, mit der Sie multiplizieren, ist eine Annäherung des Reziproken. Die zufälligen +/- 1 hier und da sind, um sicherzustellen, dass es immer richtig "gerundet" ist. Der Nachweis, dass eine bestimmte Methode korrekt ist, kann entweder mathematisch oder durch Brute-Force-Tests aller Zähler durchgeführt werden. (Für 32-Bit ist das völlig machbar.) – Mysticial

+0

@Mysticial: Das sieht nach einer Antwort auf mich aus. –

+0

@ScottHunter Vielleicht später, wenn ich arbeitslos bin. Ich habe hier nicht die richtigen Werkzeuge, um eine umfassende Antwort zu geben. – Mysticial

Antwort

10

Diese Methode heißt "Division durch Invariante Multiplikation".

Die Konstanten, die Sie sehen, sind tatsächlich Annäherungen des Reziproken.

Also anstatt die Berechnung:

N/D = Q 

Sie etwas tun, wie diese statt:

N * (1/D) = Q 

wo 1/D ein reziprok ist, die vorberechnet werden kann.

Grundsätzlich sind Kehrwerte ungenau, es sei denn ist eine Zweierpotenz. Es wird also einen Rundungsfehler geben. Die +1, die Sie sehen, ist da, um den Rundungsfehler zu korrigieren.


Das häufigste Beispiel ist die Division durch 3:

N/3 = (N * 0xaaaaaaab) >> 33 

Wo 0xaaaaaaab = 2^33/3 + 1.

Dieser Ansatz wird auf andere Teiler verallgemeinert.

+1

Die kanonische Referenz ist: T. Granlund und PL Montgomery," Division durch invariante Integer mit Multiplikation ", in * Proceedings der SIGPLAN '94 Conference on Programming Language Design und Implementierung *, 1994, S. 61-72. – njuffa

+1

Zusätzliche, neuere Referenz: N. Möller und T. Granlund, "Verbesserte Division durch invariante Integer" * IEEE Transactions on Computers *, vol. 60, nein. 2, pp. 165 -175, Feb. 2011. – njuffa

+0

Ihre Verallgemeinerung und der Beweis ist falsch. Auch das 0x55555556 für die Division durch 3 funktioniert nur für den signierten Bereich, d. bis zu 2^31. – Jester