Forum: Mikrocontroller und Digitale Elektronik Gleitkomma Multiplikation auf Atmega8


von Attila C. (attila)


Lesenswert?

Hallo!

Ich möchte einen 16 bit int mit einer Kommazahl multiplizieren und als 
Resultat ein 16 bit int erhalten das bei Bedarf natürlich auf oder 
abgerundet werden muss.

Beispiel: 1000*1,2=1200

Wie stelle ich das am geschicktesten an?

Ich vermute das das Ganze um eine Kommastelle (oder die Anzahl 
benötigter Kommastellen) nach links geschoben wird um damit zu rechnen 
aber ich denke *10 und später /10 ist eben nicht geschickt.

Ich bin mir sicher im Forum wimmelt es vor Beiträgen zu diesem Thema 
allerdings gelingt es mir nicht den richtigen Suchbegriff zu finden.

Vielen Dank schon mal!

von Peter II (Gast)


Lesenswert?

Attila Ciftci schrieb:

> Beispiel: 1000*1,2=1200

kann könnte es noch mit float rechnen, dafür musst du nur das , durch 
ein . ersetzen.

oder man teilt es einfach auf

x = 1000
x += (1000/10) * 12

von Max H. (hartl192)


Lesenswert?

Peter II schrieb:
> x = 1000
> x += (1000/10) * 12
x ist dann am Ende 2200.

von rava (Gast)


Lesenswert?

ich würde eher um 2er-Potenzen schieben, wenn dein chip keine 
hardwaredivision kann. Also

1000 * 1,2 = 1000 * 307 / 256 = 1000 * 78643 / 65536

dafür musst du nur einige zusätzliche Bits zur Verfügung stellen (16bit 
-> 32bit -> 16 bit)

die Divisionen durch 2er Potenzen sind integer-schiebeoperationen

von Peter II (Gast)


Lesenswert?

Max H. schrieb:
> Peter II schrieb:
>> x = 1000
>> x += (1000/10) * 12
> x ist dann am Ende 2200.

danke. Dann so

>> x = 1000
>> x += (1000/10) * 2

von Attila C. (attila)


Lesenswert?

Danke Peter!
Für das Benutzen von float gibt es halt immer böse Schelte ;-) hier im 
Forum!
Deine Lösung mit dem Aufteilen gefällt mir sehr gut!

von Peter II (Gast)


Lesenswert?

rava schrieb:
> ich würde eher um 2er-Potenzen schieben, wenn dein chip keine
> hardwaredivision kann. Also
>
> 1000 * 1,2 = 1000 * 307 / 256 = 1000 * 78643 / 65536
>
> dafür musst du nur einige zusätzliche Bits zur Verfügung stellen (16bit
> -> 32bit -> 16 bit)
>
> die Divisionen durch 2er Potenzen sind integer-schiebeoperationen

das ist dann aber wesentlich langsamer als

x = 1000
x += (1000/10) * 2

ist nur eine Addition und ein shift.

von Attila C. (attila)


Lesenswert?

Danke rava! Gibt es einen Artikel zu Deinem Lösungsvorschlag? Weil so 
wie Du es beschreibst verstehe ich es nicht auf Anhieb.

von Karl H. (kbuchegg)


Lesenswert?

Peter II schrieb:

> das ist dann aber wesentlich langsamer als
>
> x = 1000
> x += (1000/10) * 2
>
> ist nur eine Addition und ein shift.

Das ist aber sicherlich nicht der Regelfall. Denn in der Regel lautet 
die Aufgabe ja nicht 1000 zu behandelen, sondern x. Wodurch die Division 
durch 10 relevant wird.

von Karl H. (kbuchegg)


Lesenswert?

Attila Ciftci schrieb:
> Danke rava! Gibt es einen Artikel zu Deinem Lösungsvorschlag? Weil so
> wie Du es beschreibst verstehe ich es nicht auf Anhieb.

Eigentlich sehr einfach, wenn man es mal verstanden hat.

Ausgangspunkt ist die Division. Divisionen sind teurer als 
Multiplikationen. D.h. man möchte es dem µC einfach machen und durch 
etwas dividieren, was er gut kann.
Und das sind nun mal 2-er Potenzen. Durch 2, 4, 8, 16, ... zu dividieren 
ist leicht. Alle anderen Fälle sind schwer. Wenn man es dann noch 
besonders leicht machen möchte, dann dividieret man durch 256, oder 
65536. Denn das eine ist 2 hoch 8 und damit die Bitbreite eines Byte und 
das andere ist 2 hoch 16 und damit die Beitbreite eines uint16_t. In 
diesen Fällen ist die Division besonders einfach, denn dann muss man 
noch nicht einmal Bitschieben, sondern lässt einfach 1 oder 2 Bytes 
unter den Tisch fallen. In einem gewissen Sinnde ist das für einen µC 
das, was für dich eine Division durch 10 ist: Besonders einfach, denn um 
durch 10 zu dividieren brauchst du nicht rechnen. 4789 / 10 rechnest du 
ganz einfach, in dem du die Einerstelle wegfallen lässt und das Ergebnis 
ist 478. Ähnlich auf deinem AVR: Um durch 256 zu dividieren lässt er 
einfach das Low-Byte einer int-Zahl wegfallen.

Damit hast du ein Ziel: Du möchtest 1.2 gerne als Bruch ausdrücken, weil 
du ja x * 1.2 durch etwas der Form x * a / b ersetzen willst. Nur hast 
du gerade eine Zusatzbedingung formuliert. b sollte zumindest eine 2-er 
Potenz sein, wenn geht dann gleich 256.

Also lautet die Frage: welcher Bruch a/b ist eine gute Näherung für 1.2, 
wobei b gleich 256 sein soll.
Ein bischen Umformen bringt uns zu 1.2*b = a, ausgerechnet 1.2 * 256 = 
307.2
Da du ganzzahlige rechnen willst, willst du logischerweise nicht x * 
307.2 / 256 rechnen, sondern x*307/256
Entgegen kommt dir jetzt, dass du nur ein ganzzahliges Ergebnis 
brauchst. D.h. von dem 'Fehler', den du durch die Verwendung von 307 
anstatt 307.2 einbringst, wird schon mal einiges dadurch sich verlieren, 
dass dich sowieso nur das ganzzahlige Ergebnis interessiert. UNd für den 
Rest kann man eine ANalyse machen oder aber, man probiert (zb mit Excel) 
das ganze einfach mal am PC mit den relevanten Zahlen im relevanten 
Zahlenbereich durch, ob dort überall das richtige rauskommt. Oder ob man 
zumindest mit dem Fehler leben kann.
Ja auch das gibt es: man rechnet eigentlich ein bischen falsch aus, aber 
das macht nichts.

: Bearbeitet durch User
von rava (Gast)


Lesenswert?

einen Artikel? Ich denke nicht.

Ist aber ganz einfach:

gehst du aus von
int x = ?;
float y = ?f;

dann ist
x * y = x  y  256/256 = x*(y*256)/256

wenn du y im Voraus kennst, kannst du (y*256) vorberechnen und als 
integerzahl im code ablegen. Dann ist x*(y*256) nur noch eine 
Integermultiplikation für en Controller und aus /256 macht ein guter 
Compiler >>8 (Zahl um 8 bits nach rechts schieben).

von Rolf Magnus (Gast)


Lesenswert?

Peter II schrieb:
> oder man teilt es einfach auf
>
> x = 1000
> x += (1000/10) * 2

Wozu soll diese Aufteilung gut sein?

von Peter II (Gast)


Lesenswert?

Rolf Magnus schrieb:
> Peter II schrieb:
>> oder man teilt es einfach auf
>>
>> x = 1000
>> x += (1000/10) * 2
>
> Wozu soll diese Aufteilung gut sein?

man kann es auch als 1zeiler schreiben

1000 *1,2 = 1000 + ( 1000/10 ) * 2;

von Attila C. (attila)


Lesenswert?

Vielen Dank Karl Heinz! Wie immer erstklassig erklärt! Ich werde das mal 
so implementieren und sehen ob es für meine Anwendung funktioniert. 
Vielen Dank!

von Falk B. (falk)


Lesenswert?

Mein Gott, das Thema ist uralt, nennt sich Festkommaarithmetik.

von Rolf Magnus (Gast)


Lesenswert?

Peter II schrieb:
> Rolf Magnus schrieb:
>> Peter II schrieb:
>>> oder man teilt es einfach auf
>>>
>>> x = 1000
>>> x += (1000/10) * 2
>>
>> Wozu soll diese Aufteilung gut sein?
>
> man kann es auch als 1zeiler schreiben
>
> 1000 *1,2 = 1000 + ( 1000/10 ) * 2;

Aber welchen besonderen Vorteil hat 1000 + ( 1000/10 ) * 2 gegenüber
( 1000/10 ) * 12?

von Peter II (Gast)


Lesenswert?

Rolf Magnus schrieb:
> Aber welchen besonderen Vorteil hat 1000 + ( 1000/10 ) * 2 gegenüber
> ( 1000/10 ) * 12?

das es bei andern zahlen nicht zu "verlusten" kommt.

bei 999 macht es einen unterschied, weil 999/10 nur 99 ist.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Wenn die Kommazahl eine Konstante ist, kann man versuchen, die
spezifischen Eigenschaften dieser Zahl für ein optimiertes
Rechenverfahren zu nutzen.

Hier ist ein Beispiel für den Faktor 1,2, das mit ganz einfachen
16-Bit-Operationen auskommt und damit für 8/16-Bit-Controller ohne
Hardwaremultiplizierer sehr gut geeignet ist:

1
uint16_t mul1_2(uint16_t x) {
2
  uint16_t y, z;
3
4
  y = x >> 2;
5
  do {
6
    z = y;
7
    y = (x + z) >> 1;
8
    y = (y + z) >> 2;
9
  } while(y != z);
10
  return x + y;
11
}

Das Argument x darf dabei im Bereich 0..52428 liegen. Der Algorithmus
liefert in 1 bis 10 Iterationen (abhängig von der Größe von x) den
ganzzahligen Anteil von x·1,2.

: Bearbeitet durch Moderator
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.