Der schnellste Weg, um Integer Mod 10 und Integer Divide 10 zu bekommen?

10

Wenn eine Hardware keine Modul- oder Divisionsoperationen unterstützt, sind viel mehr CPU-Zyklen erforderlich, um den Modul / die Division durch Software zu simulieren. Gibt es eine schnellere Möglichkeit, Division und Modul zu berechnen, wenn der Operand 10 ist?

In meinem Projekt muss ich häufig den Ganzzahlmodul 10 berechnen. Insbesondere arbeite ich an PIC16F und muss eine Zahl auf einem LCD anzeigen. Es sind 4 Ziffern zu unterstützen, daher gibt es 4 Aufrufe der Modul- und Divisionsfunktion (Software-Implementierung). Das heißt, wie folgt:

digit = number % 10;    // call to an expensive function
number /= 10;           // call to an expensive function
somehow_lit_segments();

digit = number % 10;    // call to an expensive function
number /= 10;           // call to an expensive function
somehow_lit_segments();

digit = number % 10;    // call to an expensive function
number /= 10;           // call to an expensive function
somehow_lit_segments();

digit = number % 10;    // call to an expensive function
number /= 10;           // call to an expensive function
somehow_lit_segments();

Es gibt andere Bereiche, die ähnlichen Code verwenden.

Donotalo
quelle
Warum sind ein paar Dutzend Anrufe / Sek. Ein Problem? Ich würde mich nicht darum kümmern, wenn das Projekt nicht voll funktionsfähig und fehlerfrei ist.
Nick T
Ich habe festgestellt, dass die Tastenreaktion langsam wird, wenn ich kontinuierlich eine Nummer in der Hauptbelegungsschleife anzeige. Um festzustellen, dass eine Taste gedrückt wurde, muss ich diese Taste etwas länger drücken. Dies geschieht, wenn die Systemuhr 32768 Hz läuft.
Donotalo
Verwenden Sie Interrupts? Warum verwenden Sie ein 32-kHz-XTAL? Normalerweise können Sie eine geringere Leistung erzielen, wenn Sie schneller arbeiten und im Leerlauf schlafen gehen.
Nick T
Ich benutze Interrupts. Aber nur um die Anzeige zu aktualisieren, lohnt es sich nicht, auf Hochgeschwindigkeitsschwingung umzuschalten. kraftmäßig. für mein Projekt. Es muss fast 90% seiner Lebensdauer mit einer langsamen Uhr laufen.
Donotalo
2
Generell ist das Buch Hacker's Delight von Henry S. Warren Jr. die Quelle für clevere Tricks. Ich habe nach Teilungsvorschlägen gesucht, und es gibt nichts zum Teilen durch 10, was einer der folgenden Antworten überlegen ist.
RBerteig

Antworten:

11

Hier ist ein Binär-BCD-Algorithmus, den ich vor einigen Jahren verwendet habe, basierend auf einem hier gefundenen . Ich habe einen externen BCD-zu-7-Seg-Anzeigetreiber verwendet, damit das Ergebnis direkt als gepacktes BCD für die Ausgabe auf die richtigen Ports geschrieben werden kann.

Dies ist ziemlich schnell, wenn Sie einen Hardware-Multiplikator im PIC haben, ich habe einen PIC18F97J60 verwendet. Wenn Ihr PIC keinen Hardware-Multiplikator enthält, sollten Sie Shift + Add für die Multiplikation verwenden.

Dies nimmt ein vorzeichenloses 16-Bit-Int auf und gibt gepacktes BCD mit 5 Ziffern zurück. Es könnte geändert und für 4 Ziffern schneller gemacht werden. Es verwendet Shift + Additionen, um die Division durch 10 zu approximieren, aber angesichts des begrenzten Eingabebereichs ist es genau für diese Verwendung. Möglicherweise möchten Sie das Ergebnis auch anders verpacken, um festzustellen, wie Sie das Ergebnis verwenden.

void intToPackedBCD( uint16_t n, uint8_t *digits ) {

    uint8_t d4, d3, d2, d1, d0, q;  //d4 MSD, d0 LSD

    d1 = (n>>4)  & 0xF;
    d2 = (n>>8)  & 0xF;
    d3 = (n>>12) & 0xF;

    d0 = 6*(d3 + d2 + d1) + (n & 0xF);
    q = (d0 * 0xCD) >> 11;
    d0 = d0 - 10*q;

    d1 = q + 9*d3 + 5*d2 + d1;
    q = (d1 * 0xCD) >> 11;
    d1 = d1 - 10*q;

    d2 = q + 2*d2;
    q = (d2 * 0x1A) >> 8;
    d2 = d2 - 10*q;

    d3 = q + 4*d3;
    d4 = (d3 * 0x1A) >> 8;
    d3 = d3 - 10*d4;

    digits[0] = (d4<<4) | (d3);
    digits[1] = (d2<<4) | (d1);
    digits[2] = (d0<<4);
}
Kennzeichen
quelle
toller Link, danke! Es optimiert nicht nur die Geschwindigkeit, sondern verringert auch die Codegröße. Ich habe "12-Bit-Binär- zu 4 ASCII-Dezimalstellen" aus Ihrem Link implementiert, da dies keine Multiplikation beinhaltet.
Donotalo
8

Unter der Annahme von Ganzzahlen ohne Vorzeichen können Division und Multiplikation aus Bitverschiebungen gebildet werden. Und aus (ganzzahliger) Division und Multiplikation kann Modulo abgeleitet werden.

Mit 10 multiplizieren:

y = (x << 3) + (x << 1);

Durch 10 zu teilen ist schwieriger. Ich kenne mehrere Divisionsalgorithmen. Wenn ich mich richtig erinnere, gibt es eine Möglichkeit, durch Bitverschiebungen und Subtraktion schnell durch 10 zu teilen, aber ich kann mich nicht an die genaue Methode erinnern. Wenn dies nicht der Fall ist, handelt es sich um einen Divisionsalgorithmus, der <130 Zyklen verwaltet . Ich bin nicht sicher, welches Mikro Sie verwenden, aber Sie können das auf irgendeine Weise verwenden, selbst wenn Sie es portieren müssen.

EDIT: Jemand sagt bei Stack Overflow , wenn Sie ein bisschen Fehler tolerieren können und ein großes temporäres Register haben, funktioniert dies:

temp = (ms * 205) >> 11;  // 205/2048 is nearly the same as /10

Angenommen, Sie haben Division und Multiplikation, ist Modulo einfach:

mod = x - ((x / z) * z)
Thomas O.
quelle
6

Mit dem Double-Dabble-Algorithmus können Sie ohne Division von binärem zu gepacktem BCD konvertieren . Es wird nur Shift und Add 3 verwendet .

Konvertieren Sie beispielsweise 243 10 = 11110011 2 in binär

0000 0000 0000   11110011   Initialization
0000 0000 0001   11100110   Shift
0000 0000 0011   11001100   Shift
0000 0000 0111   10011000   Shift
0000 0000 1010   10011000   Add 3 to ONES, since it was 7
0000 0001 0101   00110000   Shift
0000 0001 1000   00110000   Add 3 to ONES, since it was 5
0000 0011 0000   01100000   Shift
0000 0110 0000   11000000   Shift
0000 1001 0000   11000000   Add 3 to TENS, since it was 6
0001 0010 0001   10000000   Shift
0010 0100 0011   00000000   Shift
   2    4    3
       BCD

Dieser Algorithmus ist sehr effizient, wenn kein Hardware-Divisor verfügbar ist. Darüber hinaus wird nur die Linksschaltung um 1 verwendet, sodass sie auch dann schnell ist, wenn kein Barrel Shifter verfügbar ist

phuclv
quelle
4

Abhängig von der Anzahl der benötigten Ziffern können Sie möglicherweise die Brute-Force-Methode verwenden ( d- Eingabenummer, t- Ausgabe-ASCII-Zeichenfolge):

t--;
if (d >= 1000) t++; *t = '0'; while (d >= 1000) { d -= 1000; *t += 1; }
if (d >= 100) t++; *t = '0'; while (d >= 100) { d -= 100; *t += 1;}
if (d >= 10) t++; *t = '0'; while (d >= 10) { d -= 10; *t += 1;}
t++; *t = '0' + d;

Sie können die mehreren ifs auch in eine Schleife ändern, deren Zehnerpotenzen durch Multiplikation oder eine Nachschlagetabelle erhalten werden.

jpc
quelle
2

Dieser Anwendungshinweis beschreibt Algorithmen für die BCD-Arithmetik, einschließlich der Konvertierung von binär nach BCD und umgekehrt. Die Appnote stammt von Atmel, AVR, aber die beschriebenen Algorithmen sind prozessorunabhängig.

stevenvh
quelle
1

Ich habe keine gute Antwort, aber auf unserer Schwesterseite Stack Overflow gibt es eine großartige Diskussion zum exakt gleichen Thema der Division und Modulo-Optimierung.

Haben Sie genug Speicher, um eine Nachschlagetabelle zu implementieren?

Hackers Delight hat ein Papier über optimale Teilungsalgorithmen.

Adam Lawrence
quelle
Nein, ich habe nicht genug Speicher. Ich möchte das mit Addition, Subtraktion und Bitverschiebung tun.
Donotalo
1

Haben Sie darüber nachgedacht, diesen Wert die ganze Zeit als BCD zu halten (unter Verwendung einfacher spezieller Unterroutinen "BCD-Inkrement" und "BCD-Addition"), anstatt diesen Wert in binärer Form zu halten und nach Bedarf in BCD zu konvertieren (unter Verwendung einer schwieriger zu verstehenden "Konvertierung"? von binär zu BCD "Subroutine)?

Zu einer Zeit speicherten alle Computer alle Daten als Dezimalstellen (Zahnräder mit zehn Positionen, Vakuumröhren mit zwei von fünf Codes, BCD usw.), und dieses Erbe besteht bis heute fort. (Siehe Warum verwenden Echtzeituhr-Chips BCD ).

Davidcary
quelle
Die auf dem LCD anzuzeigende Zahl ist eine Variable zwischen -1999 und 1999. Sie zeigt eine Temperatur an und wird im Binärformat berechnet.
Donotalo
1

Die PICList ist eine erstaunliche Ressource für Leute, die PIC-Prozessoren programmieren.

BCD-Konvertierung

Haben Sie darüber nachgedacht, eine bewährte Binär-zu-BCD-Subroutine von der Stange zu verwenden, die speziell für den PIC16F optimiert wurde?

Insbesondere die Benutzer der PICList haben viel Zeit damit verbracht, die Binär-BCD-Konvertierung auf einem PIC16F zu optimieren. Diese Routinen (jeweils von Hand für eine bestimmte Größe optimiert) sind unter "PIC Microcontoller Radix Conversion Math Methods" ( http://www.piclist.com/techref/microchip/math/radix/index.htm) zusammengefasst

Integer Division und Mod

Auf einer CPU wie dem PIC16F ist eine Subroutine, die auf das Teilen durch eine Konstante spezialisiert ist, häufig viel schneller als eine allgemeine Routine zum Teilen von Variablen A durch Variable B. Möglicherweise möchten Sie Ihre Konstante (in diesem Fall "0.1") in die "Codegenerierung für konstante Multiplikation / Division" http://www.piclist.com/techref/piclist/codegen/constdivmul.htm einfügen oder die Dosenroutinen in der Nähe von http://www.piclist.com/techref/microchip/math/basic.htm .

Davidcary
quelle
1

Bei einer 8x8-Hardware-Multiplikation kann ein Divmod-10 einer Zahl beliebiger Größe unter Verwendung einer Routine berechnet werden, die es für eine 12-Bit-Zahl im Bereich 0-2559 über die folgende Prozedur berechnet:

  1. Nehmen Sie die ursprüngliche Nummer in OrigH: OrigL an
  2. Teilen Sie die ursprüngliche Zahl durch zwei und speichern Sie diese in TempH: TempL
  3. Fügen Sie das MSB von TempL * 51 zum LSB von TempH * 51 hinzu. Das ist der ungefähre Quotient
  4. Multiplizieren Sie den ungefähren Quotienten mit 10 und verwerfen Sie das MSB des Werts.
  5. Subtrahieren Sie das LSB dieses Ergebnisses vom LSB der ursprünglichen Nummer.
  6. Wenn dieser Wert 10 oder mehr beträgt (max wird 19 sein), subtrahieren Sie 10 und addieren Sie 1 zum ungefähren Quotienten

Ich würde vorschlagen, eine Divmod-Routine zu schreiben, bei der das MSB der Nummer in W steht und auf das LSB von FSR hingewiesen wird. Die Routine sollte den Quotienten in FSR mit Nachdekrementierung speichern und den Rest in W belassen. Um eine 32-Bit-Länge durch 10 zu teilen, würde man dann Folgendes verwenden:

  movlw 0
  lfsr 0, _number + 3; Zeigen Sie auf MSB
  Rufen Sie _divmod10_step auf
  Rufen Sie _divmod10_step auf
  Rufen Sie _divmod10_step auf
  Rufen Sie _divmod10_step auf

Ein divmod-6-Schritt wäre sehr ähnlich, außer dass Konstanten von 85 und 6 anstelle von 51 und 10 verwendet würden. In beiden Fällen würde ich erwarten, dass der divmod10_step 20 Zyklen (plus vier für den Aufruf / die Rückgabe) beträgt, also ein kurzer divmod10 ungefähr 50 Zyklen sein und ein langer divmod10 wäre ungefähr 100 (wenn man den ersten Schritt in Sonderfällen macht, könnte man ein paar Zyklen sparen).

Superkatze
quelle
1

Dies ist möglicherweise nicht der schnellste, aber ein einfacher Weg.

 a = 65535;

    l = 0;
    m = 0;
    n = 0;
    o = 0;
    p = 0;

    while (a >= 10000)
    {   a -= 10000;
        l += 1;
    }
     while (a >= 1000)
    {   a -= 1000;
        m += 1;
    }
     while (a >= 100)
    {   a -= 100;
        n += 1;
    }
     while (a >= 10)
    {   a -= 10;
        o += 1;
    }
     while (a > 0)
    {   a -= 1;
        p += 1;
    }
Sergiu
quelle