Wie kann ich multiplizieren und dividieren, indem ich nur Bitverschiebung und Addition verwende?

Lesezeit: 8 Minuten

Benutzeravatar von Spidfire
Spidfire

Wie kann ich multiplizieren und dividieren, indem ich nur Bitverschiebung und Addition verwende?

  • So wie Sie es in der Mittelschule auf dem Papier machen würden, nur binär statt dezimal.

    – Pascal Cuoq

    5. Mai 2010 um 19:36 Uhr

  • @mtk: Was fehlt in dieser Antwort? Suchen Sie nach einer C- oder Assembler-Implementierung, bestimmten Operandenbreiten, einer bestimmten Divisionsmethode (z. B. Wiederherstellen vs. Nicht-Wiederherstellen)?

    – njuffa

    7. September 2015 um 7:05 Uhr

  • Ist die Subtraktion in Ordnung? Alles scheint abgedeckt zu sein

    – mksteve

    9. September 2015 um 20:40 Uhr

  • Welche Notwendigkeit steckt hinter dieser Frage? CPUs übersetzen Multiplikations- und Divisionsoperationen bereits in Bitverschiebung und Addition oder Subtraktion, und wenn das der Fall ist, wenn der Compiler dies noch nicht getan hat.

    – Kelly S. Französisch

    13. Juni 2016 um 15:28 Uhr

  • @KellyS.French Nur Neugier, es ist eher eine Möglichkeit, sich vorzustellen, wie ein Compiler mit einem eingeschränkten Befehlssatz arbeiten kann.

    – Spidfire

    14. Juni 2016 um 8:13 Uhr

Um in Bezug auf Addieren und Verschieben zu multiplizieren, möchten Sie eine der Zahlen wie folgt in Zweierpotenzen zerlegen:

21 * 5 = 10101_2 * 101_2             (Initial step)
       = 10101_2 * (1 * 2^2  +  0 * 2^1  +  1 * 2^0)
       = 10101_2 * 2^2 + 10101_2 * 2^0 
       = 10101_2 << 2 + 10101_2 << 0 (Decomposed)
       = 10101_2 * 4 + 10101_2 * 1
       = 10101_2 * 5
       = 21 * 5                      (Same as initial expression)

(_2 bedeutet Basis 2)

Wie Sie sehen können, kann die Multiplikation in Addition und Verschiebung und wieder zurück zerlegt werden. Aus diesem Grund dauert die Multiplikation auch länger als das Verschieben oder Addieren von Bits – es ist O (n ^ 2) und nicht O (n) in der Anzahl der Bits. Reale Computersysteme (im Gegensatz zu theoretischen Computersystemen) haben eine endliche Anzahl von Bits, sodass die Multiplikation im Vergleich zur Addition und Verschiebung ein konstantes Vielfaches an Zeit in Anspruch nimmt. Wenn ich mich richtig erinnere, können moderne Prozessoren bei richtiger Pipeline Multiplikation genauso schnell wie Addition durchführen, indem sie mit der Auslastung der ALUs (Arithmetikeinheiten) im Prozessor herumspielen.

  • Ich weiß, es ist eine Weile her, aber könnten Sie ein Beispiel mit Division geben? Vielen Dank

    – GniruT

    14. Oktober 2015 um 13:23 Uhr

Benutzeravatar von Viktor Latypov
Viktor Latypow

Die Antwort von Andrew Toulouse kann auf Division ausgedehnt werden.

Die Division durch ganzzahlige Konstanten wird ausführlich in dem Buch “Hacker’s Delight” von Henry S. Warren (ISBN 9780201914658) behandelt.

Die erste Idee zur Implementierung der Division besteht darin, den Kehrwert des Nenners in die Basis zwei zu schreiben.

Z.B,
1/3 = (base-2) 0.0101 0101 0101 0101 0101 0101 0101 0101 .....

So,
a/3 = (a >> 2) + (a >> 4) + (a >> 6) + ... + (a >> 30)
für 32-Bit-Arithmetik.

Indem wir die Begriffe auf naheliegende Weise kombinieren, können wir die Anzahl der Operationen reduzieren:

b = (a >> 2) + (a >> 4)

b += (b >> 4)

b += (b >> 8)

b += (b >> 16)

Es gibt aufregendere Möglichkeiten, Divisionen und Reste zu berechnen.

EDIT1:

Wenn das OP Multiplikation und Division beliebiger Zahlen bedeutet, nicht die Division durch eine konstante Zahl, dann könnte dieser Thread von Nutzen sein: https://stackoverflow.com/a/12699549/1182653

EDIT2:

Eine der schnellsten Möglichkeiten, durch ganzzahlige Konstanten zu dividieren, besteht darin, die modulare Arithmetik und die Montgomery-Reduktion auszunutzen: Was ist der schnellste Weg, eine ganze Zahl durch 3 zu teilen?

  • Vielen Dank für die Hacker’s Delight-Referenz!

    – Alekse

    6. August 2015 um 17:35 Uhr

  • Ehm ja, diese Antwort (Division durch Konstante) ist nur teilweise Korrekt. Wenn Sie versuchen, 3/3 zu machen, erhalten Sie am Ende 0. In Hacker’s Delight erklären sie tatsächlich, dass es einen Fehler gibt, den Sie kompensieren müssen. In diesem Fall: b += r * 11 >> 5 mit r = a - q * 3. Verknüpfung: hackersdelight.org/divcMore.pdf Seite 2+.

    – Atlas

    18. April 2016 um 8:30 Uhr


  • Es sollte klargestellt werden, dass die Lösung der Antwort nicht für die allgemeine Fallteilung gilt, sondern für einige spezielle Fälle mit konstanten Divisoren, z Division durch 3, Division durch 5 oder Division durch 7. Und die Antwort zeigt ein Beispiel für Division durch 3. Überprüf den Hacker’s Delight Buch für mehr.

    – Rick

    25. Januar um 6:28 Uhr


Benutzeravatar von Kelly S. French
Kelly S. Französisch

X * 2 = 1-Bit-Verschiebung nach links
X / 2 = 1-Bit-Verschiebung nach rechts
X * 3 = 1 Bit nach links verschieben und dann X hinzufügen

  • Meinst du add X für das letzte?

    – Mark Byers

    5. Mai 2010 um 19:39 Uhr

  • Es ist immer noch falsch – die letzte Zeile sollte lauten: “X * 3 = 1 Bit nach links verschieben und dann X hinzufügen”

    – PaulR

    5. Mai 2010 um 21:02 Uhr


  • “X / 2 = 1 Bit Verschiebung nach rechts”, nicht ganz, es wird auf unendlich abgerundet und nicht auf 0 (für negative Zahlen), was die übliche Implementierung der Division ist (zumindest soweit ich gesehen habe).

    – Leif Andersen

    27. August 2011 um 18:26 Uhr


Benutzeravatar von IVlad
IVlad

x << k == x multiplied by 2 to the power of k
x >> k == x divided by 2 to the power of k

Sie können diese Verschiebungen verwenden, um jede Multiplikationsoperation durchzuführen. Zum Beispiel:

x * 14 == x * 16 - x * 2 == (x << 4) - (x << 1)
x * 12 == x * 8 + x * 4 == (x << 3) + (x << 2)

Um eine Zahl durch eine Zweierpotenz zu dividieren, ist mir kein einfacher Weg bekannt, es sei denn, Sie möchten eine Low-Level-Logik implementieren, andere binäre Operationen verwenden und eine Form der Iteration verwenden.

  1. Eine Linksverschiebung um 1 Stelle entspricht einer Multiplikation mit 2. Eine Rechtsverschiebung entspricht einer Division durch 2.
  2. Sie können eine Schleife hinzufügen, um zu multiplizieren. Indem Sie die Schleifenvariable und die Additionsvariable richtig auswählen, können Sie die Leistung begrenzen. Sobald Sie das erforscht haben, sollten Sie verwenden Bauernvermehrung

  • +1: Aber die Linksverschiebung ist nicht nur analog zur Multiplikation mit 2. Es ist Multiplizieren mit 2. Zumindest bis zum Überlauf …

    – Don Roby

    5. Mai 2010 um 22:20 Uhr

  • Shift-Division liefert falsche Ergebnisse für negative Zahlen.

    – David

    5. Februar 2017 um 3:57 Uhr

Ein Verfahren zur Division ganzer Zahlen, das Verschiebungen und Additionen verwendet, kann auf einfache Weise von der dezimalen handschriftlichen Division abgeleitet werden, wie sie in der Grundschule gelehrt wird. Die Auswahl jeder Quotientenziffer wird vereinfacht, da die Ziffer entweder 0 oder 1 ist: Wenn der aktuelle Rest größer oder gleich dem Divisor ist, ist das niedrigstwertige Bit des Teilquotienten 1.

Genau wie bei der Dezimaldivision werden die Ziffern des Dividenden von der höchsten bis zur niedrigsten Ziffer betrachtet. Dies wird leicht durch eine Linksverschiebung in der binären Division erreicht. Außerdem werden Quotientenbits gesammelt, indem die aktuellen Quotientenbits um eine Position nach links verschoben werden und dann das neue Quotientenbit angehängt wird.

In einer klassischen Anordnung werden diese beiden Linksverschiebungen zu einer Linksverschiebung eines Registerpaares kombiniert. Die obere Hälfte enthält den aktuellen Rest, die untere Hälfte initial den Dividenden. Da die Dividendenbits durch Linksverschiebung in das Restregister übertragen werden, werden die unbenutzten niedrigstwertigen Bits der unteren Hälfte verwendet, um die Quotientenbits zu akkumulieren.

Nachfolgend finden Sie die x86-Assemblersprache und C-Implementierungen dieses Algorithmus. Diese spezielle Variante einer Shift & Add-Division wird manchmal als “nicht funktionierende” Variante bezeichnet, da die Subtraktion des Divisors vom aktuellen Rest nicht durchgeführt wird, es sei denn, der Rest ist größer oder gleich dem Divisor (Otto Spaniol, „Computerarithmetik: Logik und Design.“ Chichester: Wiley 1981, S. 144). In C gibt es keine Vorstellung von dem Carry-Flag, das von der Assembler-Version in der Linksverschiebung des Registerpaars verwendet wird. Stattdessen wird es emuliert, basierend auf der Beobachtung, dass das Ergebnis einer Addition modulo 2 istn kann nur dann kleiner als jeder Summand sein, wenn es einen Übertrag gab.

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

#define USE_ASM 0

#if USE_ASM
uint32_t bitwise_division (uint32_t dividend, uint32_t divisor)
{
    uint32_t quot;
    __asm {
        mov  eax, [dividend];// quot = dividend
        mov  ecx, [divisor]; // divisor
        mov  edx, 32;        // bits_left
        mov  ebx, 0;         // rem
    $div_loop:
        add  eax, eax;       // (rem:quot) << 1
        adc  ebx, ebx;       //  ...
        cmp  ebx, ecx;       // rem >= divisor ?
        jb  $quot_bit_is_0;  // if (rem < divisor)
    $quot_bit_is_1:          // 
        sub  ebx, ecx;       // rem = rem - divisor
        add  eax, 1;         // quot++
    $quot_bit_is_0:
        dec  edx;            // bits_left--
        jnz  $div_loop;      // while (bits_left)
        mov  [quot], eax;    // quot
    }            
    return quot;
}
#else
uint32_t bitwise_division (uint32_t dividend, uint32_t divisor)
{
    uint32_t quot, rem, t;
    int bits_left = CHAR_BIT * sizeof (uint32_t);

    quot = dividend;
    rem = 0;
    do {
            // (rem:quot) << 1
            t = quot;
            quot = quot + quot;
            rem = rem + rem + (quot < t);

            if (rem >= divisor) {
                rem = rem - divisor;
                quot = quot + 1;
            }
            bits_left--;
    } while (bits_left);
    return quot;
}
#endif

  • +1: Aber die Linksverschiebung ist nicht nur analog zur Multiplikation mit 2. Es ist Multiplizieren mit 2. Zumindest bis zum Überlauf …

    – Don Roby

    5. Mai 2010 um 22:20 Uhr

  • Shift-Division liefert falsche Ergebnisse für negative Zahlen.

    – David

    5. Februar 2017 um 3:57 Uhr

Benutzeravatar von Peter Mortensen
Peter Mortensen

Ich habe den Python-Code in C übersetzt. Das angegebene Beispiel hatte einen kleinen Fehler. Wenn der Dividendenwert alle 32 Bits belegt, würde die Verschiebung fehlschlagen. Ich habe gerade intern 64-Bit-Variablen verwendet, um das Problem zu umgehen:

int No_divide(int nDivisor, int nDividend, int *nRemainder)
{
    int nQuotient = 0;
    int nPos = -1;
    unsigned long long ullDivisor = nDivisor;
    unsigned long long ullDividend = nDividend;

    while (ullDivisor <  ullDividend)
    {
        ullDivisor <<= 1;
        nPos ++;
    }

    ullDivisor >>= 1;

    while (nPos > -1)
    {
        if (ullDividend >= ullDivisor)
        {
            nQuotient += (1 << nPos);
            ullDividend -= ullDivisor;
        }

        ullDivisor >>= 1;
        nPos -= 1;
    }

    *nRemainder = (int) ullDividend;

    return nQuotient;
}

  • Was ist mit einer negativen Zahl? Ich habe -12345 mit 10 mit Eclipse + CDT getestet, aber das Ergebnis war nicht so gut.

    – Kenmux

    23. Juni 2016 um 2:46 Uhr

  • Können Sie mir bitte sagen, warum Sie das tun ullDivisor >>= 1 Vor dem while Schleife? Auch nicht nPos >= 0 Mache den Trick?

    – Vivekanand V

    17. September 2020 um 20:12 Uhr

  • @kenmux Sie müssen nur die Größe der beteiligten Zahlen berücksichtigen, zuerst den Algorithmus ausführen und dann mithilfe einiger geeigneter Entscheidungsanweisungen das richtige Vorzeichen an den Quotienten / Rest zurückgeben!

    – Vivekanand V

    17. September 2020 um 20:14 Uhr

  • @VivekanandV Du meinst, das Schild hinzufügen – später? Ja es funktioniert.

    – Kenmux

    25. September 2020 um 3:40 Uhr

1421340cookie-checkWie kann ich multiplizieren und dividieren, indem ich nur Bitverschiebung und Addition verwende?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy