Klassisch (und lesbar ;-)) würde man die Berechnung so schreiben:
1 | zehner = zahl / 10;
|
2 | einer = zahl - 10 * zehner;
|
3 | bcd = 16 * zehner + einer;
|
Einsetzen von Zeile 2 in Zeile 3 ergibt:
1 | zehner = zahl / 10;
|
2 | bcd = 16 * zehner + zahl - 10 * zehner;
|
Das kann man noch etwas zusammenfassen:
1 | zehner = zahl / 10;
|
2 | bcd = 6 * zehner + zahl;
|
Einsetzen von Zeile 1 in Zeile 2 ergibt:
1 | bcd = 6 * (zahl / 10) + zahl;
|
Das ist vermutlich die C-Schreibweise mit der kleinsten Anzahl von
Operatoren.
Jetzt muss die Division noch wegoptimiert werden:
Eine Division durch eine Konstante kann prozessorfreundlich durch eine
Multiplikation und eine Division durch eine Zweierpotenz angenähert
werden. Da
wäre eine Näherung für zahl / 10:
1 | zahl / 10 ≈ zahl * 26 / 256 = (zahl * 26) >> 8
|
Diese Näherung hat sich aber als zu ungenau herausgestellt. Eine bessere
Nährung erhält man mit der nächsthöheren Zweierpotenz:
1 | zahl / 10 ≈ zahl * 51 / 512 = (zahl * 51) >> 9
|
Diese Näherung liefert aber in einigen Fällen immer noch falsche,
nämlich zu kleine Ergebnisse. Also wird versucht, dies durch Addition
einer Korrekturkonstanten c zu beheben:
1 | zahl / 10 ≈ (zahl * 51 + c) / 512 = (zahl * 51 + c) >> 9
|
Es stellt sich heraus, dass die Ergebnisse stimmen, wenn c im Bereich
von 18 bis 52 liegt. Setzt man c = 52, erhält man insgesamt den
folgenden Ausdruck:
1 | bcd = 6 * ((zahl * 51 + 52) >> 9) + zahl;
|
Das ist schon fast das Ergebnis aus meinem letzten Beitrag. Der GCC tut
sich etwas leichter, wenn man statt um 9 nur um 8 Bits shiftet und dafür
den Faktor 6 halbiert. Da mit diesem Vorgehen praktisch eine Division
durch 2 und eine anschließende Multiplikation mit 2 entfällt, muss
zusätzlich der Klammerausdruck auf die nächstkleinere gerade Zahl
abgerundet werden. Dazu wird einfach das letzte Bit gelöscht:
1 | bcd = 3 * ((zahl * 51 + 52) >> 8 & ~1) + zahl;
|
Während ich dies schrieb, ist mir noch eingefallen, dass man für c auch
51 einsetzen könnte, was den Ausdruck weiter vereinfacht, weil dann die
linke Addition vor der Multiplikation und damit im 8-Bit-Wertebereich
gerechnet werden kann:
1 | bcd = 3 * (((uint8_t)(zahl + 1) * 51) >> 8 & ~1) + zahl;
|