PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Rechnen mit Gleitkommazahlen



daniel.weber
07.05.2011, 16:04
Hallo zusammen,

ich habe einen Integer Wert, der den Bereich -255 bis 255 abdecken sollte. Der Wert soll sich aus folgender Rechnung ergeben:

(100/65536)* wert * 100;

Nun wird da ja eine Zahl raus kommen, die nicht in ein Integer passt, ich bin davon ausgegangen, dass die Nachkommastellen einfach weggelassen werden. Es kommt aber immer 0 raus?

da_reefer
07.05.2011, 16:48
Probier mal Wert*10000/65536, das ist rundungstechnisch auf jeden Fall besser.
Ohne Code ist es schwierig was zu sagen, aber bist du dir sicher, dass der Compiler bei der Rechnung auch intern Floats benutzt? Ich mir nämlich nicht :)

mfg

Felix G
07.05.2011, 16:52
Die Division durch 65536 ist das Problem, denn der Compiler interpretiert alle Werte als Integer (es sei denn, wert ist ein float).

Eine mögliche Lösung sähe so aus:

(int)((100.0f / 65536.0f) * (float)wert * 100.0f);
oder
(int)(((float)100 / (float)65536) * (float)wert * (float)100);Es gibt aber auch noch eine etwas elegantere Lösung, wenn man die Reihenfolge geringfügig ändert:

(100 * 100 * wert) >> 16;
bzw.
(10000 * wert) >> 16;Hier habe ich die rechenintensive Division durch einen einfachen Rechtsshift ersetzt, der wesentlich schneller ist (geht immer, wenn man durch 2^n dividieren möchte). Wichtig dabei ist aber, daß die Multiplikation vorher erfolgt, denn sonst entsteht wieder das selbe Problem wie bei deiner Variante.

Ceos
07.05.2011, 16:53
100/65536 = 0 .... die nachkommazahlen werden abgeschnitten!

und alles multipliziert mit null bleibt null ^^

stell es doch ein wenig um, erst alles multiplizieren und DANN dividieren, aber achtung, pass auf dass bei dem multiplizieren der wert nicht überläuft!

Besserwessi
07.05.2011, 17:30
Damit es beim Multiplizieren keinen Überlauf gibt kann man die Berechnung mit 32 Bit Zahler erzwischen, indem man die Konstante 10000 als 10000L schreibt und so zu einem Long wert macht.

Ceos
07.05.2011, 18:34
wenn man auf den Zahlenbereich achtet, sollte man sich den Rechenvorteil der Prozessorarchitektur nicht durch Verwendung von long Werten zunichte machen finde ich, außerdem habe ich vergessen zu erwähnen, dass man am besten einen expliziten cast auf (signed int) der Formel voranstellt, damit der compiler auch das Vorzeichen mitnimmt!

Besserwessi
07.05.2011, 18:52
Um eine Rechnung mit 32 Bit kommt man in C kaum herum:
Wenn das Ergebnis bis 255 gehen kann, passt das Ergebnis * 65536 gerade nicht mehr in 24 Bit, und schon gar nicht in 16 Bits.

In die 24 Bit würde man es gerade noch bekommen indem man
((25*25)*wert) / (4096) rechnet.
Aber 24 Bit Zahlen werden von GCC, wie fast allem C Compilern nicht unterstützt, das hilft einem also auch nicht weiter. Genauso fehlt die Multiplication 16 Bit * 16 Bit -> 32 Bit oder besser noch nach 24 Bit.

markusj
07.05.2011, 18:59
(10000 * wert) >> 16;Hier habe ich die rechenintensive Division durch einen einfachen Rechtsshift ersetzt, der wesentlich schneller ist (geht immer, wenn man durch 2^n dividieren möchte). Wichtig dabei ist aber, daß die Multiplikation vorher erfolgt, denn sonst entsteht wieder das selbe Problem wie bei deiner Variante.

ACHTUNG: Bei vorzeichenbehafteten Zahlen funktioniert der Rechtsshift nur in bestimmten Situationen (Vielfache von 8), da er NICHT zur Null hin rundet. Beispiel (für 3-Bit-Integer):

-1 >> 1 = -1 // Falsch
-2 >> 1 = -1
-3 >> 1 = -2 // Falsch
-4 >> 1 = -2Allerdings kann man den entstehenden Fehler auch ausbügeln (in diesem Beispiel durch eine Addition von 1)

mfG
Markus

sternst
07.05.2011, 21:27
Es gibt aber auch noch eine etwas elegantere Lösung, wenn man die Reihenfolge geringfügig ändert:

(100 * 100 * wert) >> 16;
bzw.
(10000 * wert) >> 16;Hier habe ich die rechenintensive Division durch einen einfachen Rechtsshift ersetzt, der wesentlich schneller ist (geht immer, wenn man durch 2^n dividieren möchte).Dieser Unsinn ist wohl auch einfach nicht totzukriegen.

Eine solche Simpelst-Optimierung macht natürlich auch der Compiler selber. Du gewinnst mit dieser Schreibweise also rein gar nichts. Du verlierst nur etwas, nämlich die Offensichtlichkeit der eigentlichen Absicht hinter dem Code.

Also: wenn du eigentlich dividieren willst, dann benutze '/', und wenn du schieben willst, dann benutze '>>'. Das Eine durch das Andere zu ersetzen, weil es schneller ist (es gibt übrigens auch Prozessoren, wo die Division schneller ist), kannst du getrost dem Compiler überlassen.

Peter1060
07.05.2011, 22:47
moin moin,

warum so viel Aufwand?
(100/65536)* wert * 100 -->> das ist doch 1Byte * ~1,52...

Ich würde da eine kleine Tabelle nehmen, es sind 256 Werte zu 16Bit notwendig.

so:
ausWert = Tabelle[abs(einWert)]
if Signum(einWert) then ausWert = - ausWert


Warum soll da überhaupt was umgerechnet werden, was ist der Hintergrunf?

Mit Gruß
Peter

Felix G
08.05.2011, 13:36
Eine solche Simpelst-Optimierung macht natürlich auch der Compiler selber. Du gewinnst mit dieser Schreibweise also rein gar nichts. Du verlierst nur etwas, nämlich die Offensichtlichkeit der eigentlichen Absicht hinter dem Code.Zugegeben, bei konstantem Divisor gewinnt man üblicherweise nichts. Ist der Divisor aber nicht konstant, kann man Divisionen deutlich beschleunigen indem man sie in eine Multiplikation und einen Rechtsshift umbaut (denn das kann der Compiler selbst nicht mehr optimieren).

Daß das nur für Controller gilt die keinen schnellen Hardware Dividierer haben, versteht sich wohl von selbst.

sternst
08.05.2011, 16:21
Ist der Divisor aber nicht konstant, kann man Divisionen deutlich beschleunigen indem man sie in eine Multiplikation und einen Rechtsshift umbaut (denn das kann der Compiler selbst nicht mehr optimieren).Und haben wir hier einen solchen Fall? Nein, es geht ganz konkret um eine Division durch 65536. Und du empfiehlst, dass durch ">> 16" zu ersetzen, weil es "eleganter" und "schneller" wäre. Sorry, aber das ist eben ziemlicher Unsinn.

Und so nebenbei: Zeig mal, wie du das mit einem nicht konstanten (und damit unbekannten) Divisor machst. Was du wohl eher meinst, ist ein konstanter Divisor, der aber keine 2er-Potenz ist.

markusj
08.05.2011, 19:25
Ich würde da eine kleine Tabelle nehmen, es sind 256 Werte zu 16Bit notwendig.

Eieiei, warum nicht gleich ein Koprozessor? Oder halt, noch besser: Wir rüsten gleich auf einen ARM der neuesten Generation um ... oder vielleicht nicht doch einen Intel Core iSonstwas Extreme?

Eine kleine 32-Bit Multiplikation (wert * 10000) mit nachfolgender Division/Wegwerfen der letzten zwei Bytes schafft der AVR auch noch ohne Lookup-Table oder sonstige Geschütze die hier aufgefahren werden. Vielleicht nicht schneller, aber vermutlich Platzsparender als eine Lookup-Table mit 2^12 Einträgen. (Ergibt sich, wenn du ausrechnest, in welchem Wertebereich "wert" liegen kann, damit sich nach der genannten Formel Werte von +-255 ergeben)

mfG
Markus

Peter1060
08.05.2011, 20:41
>>2^12 Einträgen
das ist doch Unfug, wenn 2^8.

...die Tabelle sind 512 Bytes plus Prog zum auslesen.
Der TO keinen Prof. oder Sprache angegeben hat, kann keiner sagen, wieviel Speicher notwendig ist.

Da ich in 8051 Assembler arbeite, könnte ich den genauen Bedarf für diese Sache ermitteln.

Mit Gruß
Peter

markusj
08.05.2011, 21:44
Dann leg doch Mal los.
Die Formel war:
ausgabe = 10000 * eingabe / 2^16
Umgestellt nach "eingabe" (die du ja in die Lookup-Table reinwerfen musst) ergäbe:

eingabe = ausgabe * 2^16 / 10000
"ausgabe" liegt zwischen -255 und 255 (siehe Firstpost), damit muss "eingabe" zwischen -1671 und 1671 liegen, du brauchst also 3343 Einträge in der Tabelle, auf die nächste Zweierpotenz gerundet macht das 2^12 ...
Wie du da mit 2^8 auskommen möchtest, kann ich gerade nicht sehen, auch in Assembler schlägst du die Mathematik nicht.

mfG
Markus

Peter1060
08.05.2011, 22:39
habe ich oben doch schon geschrieben:

(100/65536)* wert * 100 -->> das ist doch 1Byte * ~1,52...

ausgabe = wert *1.5258

markusj
08.05.2011, 22:46
Problem: "wert" (bei mir oben "eingabe") ist, wie ich vorgerechnet habe, eben nicht 1 Byte (Kein uint8_t).

Peter1060
08.05.2011, 23:33
>>ch habe einen Integer Wert, der den Bereich -255 bis 255 abdecken sollte

und das ist Signum + uint8

;Berechnung Ausgabe = Eingabe * (10^5 / 65536)
;
;Float
;Speicherbedarf 621 Bytes
;Takte 293 Cyclen

;Tabelle
;Speicherbedarf 544 Bytes
;Takte 23 Cyclen
;

HW_FPU EQU 0 ;keine FPU

; EinWert wird in DPTR als sign.Int übergeben
; AusWert wird in DPTR als unsigniert INT übergeben, Signum in A
;---------------------------------------------

cond 1
Mov A, DPH
Push A ;signum merken
CLR A.7
Mov DPH, A
Call _uint2fs
PutA
PushFC 1.525878906 ;10^5 / 65536
GoSub _fsmul_A
Call _round_A
Pop A ;signum dazu
ANL A, #80H
RET

include Float_s.inc

Else
;---------------------------------------------

Mov A, DPL
Add A, #TABELLE
Mov DPL, A
Mov A, DPH
Push A ;signum merken
CLR A.7
AddC A, #TABELLE >> 8
Mov DPH, A
CLR A
MovC A, @A+DPTR
Push A
Mov A,#1
MovC A, @A+DPTR
Mov DPL, A
POP DPH
POP A
ANL A, #80H
RET

TABELLE: DS 512
EndC
;---------------------------------------------
END

markusj
09.05.2011, 11:21
Der Wert soll sich aus folgender Rechnung ergeben:

(100/65536)* wert * 100;
Bequemerweise hast du DEN Teil von Daniels Aussage vergessen ... die +-255 sind Ausgabe und nicht Eingabe. Und da du in deine LUT die Eingabe stecken musst, bringt das ganze nichts. (Außer du möchtest einen Reverse-Lookup implementieren, was dann doch etwas übertrieben ist ...)

mfG
Markus

Peter1060
09.05.2011, 14:44
WENN es so ist...
dann ist der EinWert also nur im Bereich -167 bis +167...und die Tabellenform wird noch kürzer...

solange Daniel sich nicht äussert ist jede weitere Diskusion zwecklos.

Mit Gruß
Peter

XCircle
09.05.2011, 15:38
Wenn es unbedingt als Integer gemacht werden muss,
dann klappt das ganze so in C:

int wert;
printf("(100/65536)* %i * 100 = %.2f", wert, (100.0/65536.0)* wert * 100.0);