Forum: Mikrocontroller und Digitale Elektronik richtig Runden nach Ganzzahl Operation


von Jens (Gast)


Lesenswert?

Hi,

ich habe folgenes Problem:

uint16_t erg=0, test=43;

erg=(test*1000/1440);

liefert: 29

Rückrechnung 29*1440/1000 =41,7

test kann was bis 55 sein.

Wie bekomme ich das genauer.

In diesem Beispiel wäre 30 als "Ergebnis" genauer. ->43,2

Wie runde ich mit wenig Aufwand, damit es für alle Zahlen der Reihe bis 
55 besser passt?

Danke.

Maddin

von Irgend W. (Firma: egal) (irgendwer)


Lesenswert?

Jens schrieb:
> Wie runde ich mit wenig Aufwand, damit es für alle Zahlen der Reihe bis
> 55 besser passt?

Das ganze hat mit math. Runden überhaupt nichts zu tun. Du rechnest mit 
ganzen Zahlen und da es dort keine Nachkommastellen gibt werden diese 
quasi abgeschnitten und somit kann man da auch nichts mehr "runden".

Schau dir mal das an:
- https://www.mikrocontroller.net/articles/Festkommaarithmetik

von Tassilo H. (tassilo_h)


Lesenswert?

Würdest du in floating point rechnen, wäre es
(uint16_t)( (test*1000.0)/1440.0 + 0.5 )
für richtiges Runden.

Zurück zur Ganzzahlarithmetik ziehst du die 0.5 vor die Division, also

(test*1000u + 720u)/1440u

von Xerxes (Gast)


Lesenswert?

Einfach mit größeren Zahlen arbeiten.

43 * 1.000.000 / 1440 ergibt 29861

Das zurückgerechnet ergibt wieder genau 43.

von HildeK (Gast)


Lesenswert?

43*1000/1440 ist nun mal 29,861..
Integer-Rechnung liefert dann 29, völlig korrekt.
In Real würde man noch 0.5 addieren und dann die Vorkommastellen nehmen.

Was du tun kannst, ist zu rechnen: test*10000/1440+5. Das gibt dann 
303.6, dann noch durch 10 teilen.

Oder, da dann der uint32_t nicht reicht:
(test*1000/144+5)/10

von Stefan (Gast)


Lesenswert?

Hallo,

das was du brauchst ist ein Trick aus der Festkommaarithmetik

1000/1440 ist etwa gleich 711/1024 (Unterschied in der 1/10000 Stelle)
und deshalb rechnets du besser

erg = test*711/1024

die Multiplikation test*711 läuft normal
die Division .../1024 durch rechtsschiften

und mit Runden auf eine halbe Stelle ist es dann

erg = (test*711 + 256)/1024

Vorsicht: mit uint16_t auf Zahlenüberlauf achten

Gruss

von Stefan (Gast)


Lesenswert?

Ich muss mich korrigieren


erg = (test*711 + 512)/1024

512 ist die Hälfte von 1024

von Uwe B. (Firma: TU Darmstadt) (uwebonnes)


Lesenswert?

HildeK schrieb:
> 43*1000/1440 ist nun mal 29,861..
M.e.a. rundet ((43*1000) + (1440/2))/1440 richtig

von jo (Gast)


Lesenswert?

Stefan schrieb:
> das was du brauchst ist ein Trick aus der Festkommaarithmetik
>
> 1000/1440 ist etwa gleich 711/1024 (Unterschied in der 1/10000 Stelle)
> und deshalb rechnets du besser
>
> erg = test*711/1024
>
> die Multiplikation test*711 läuft normal
> die Division .../1024 durch rechtsschiften

Den Trick predige ich, in der einen oder anderen Form, seit Jahr und 
Tag.
Inzwischen hab ich's aufgegeben.

Du fragst dich, was jetzt mit deinem Tipp passieren wird? Ich denke du 
willst das gar nicht wissen...

Wenn du Glück hast, großes Glück, dann fragt einer nach, wie Du auf 
711/1024 gekommen bist.

Von mir jedenfalls ein :+1:

von A. S. (Gast)


Lesenswert?

Uwe B. schrieb:
> M.e.a. rundet ((43*1000) + (1440/2))/1440 richtig

Ja. Runden = vorher halben Teiler zu addieren. Negative Zahlen sind ein 
eigenes Thema!

Stefan schrieb:
> 1000/1440 ist etwa gleich 711/1024

Das ist besser, hat für sich gesehen mit dem Problem nichts zu tun. Es 
ist eine Optimierung. Wenn es funktioniert aber zu viel Platz oder 
Laufzeit braucht. Auch hier fällt man mit negativen Zahlen auf die Nase.

von Achim M. (minifloat)


Lesenswert?

Jens schrieb:
> Danke.
> Maddin

...und ward nicht mehr gesehen.
10/10 Ghost-TO-Punkte, leider nur
 2/10 Troll-Punkte

Zu Fractional-Arithmetic mit HildeK's Tipp hätte er sich schon mal 
wieder melden können. Viele Mikrocontroller unterstützen 
Fractional-Arithmetic im Befehlssatz, wie z.B. s16*s16-Multiplikation, 
die das Ergebnis um 15bits nach rechts geschoben wieder in ein 
16bit-Register schmeißt.

mfg mf

: Bearbeitet durch User
von Veit D. (devil-elec)


Lesenswert?

jo schrieb:
> Stefan schrieb:
>> das was du brauchst ist ein Trick aus der Festkommaarithmetik
>>
>> 1000/1440 ist etwa gleich 711/1024 (Unterschied in der 1/10000 Stelle)
>> und deshalb rechnets du besser
>>
>> erg = test*711/1024
>>
>> die Multiplikation test*711 läuft normal
>> die Division .../1024 durch rechtsschiften
>
> Den Trick predige ich, in der einen oder anderen Form, seit Jahr und
> Tag.
> Inzwischen hab ich's aufgegeben.
>
> Du fragst dich, was jetzt mit deinem Tipp passieren wird? Ich denke du
> willst das gar nicht wissen...
>
> Wenn du Glück hast, großes Glück, dann fragt einer nach, wie Du auf
> 711/1024 gekommen bist.

Hallo,

die Frage wollte ich eigentlich stellen, bin aber selbst draufgekommen.
Verhältnisgleichung bzw. Normierung auf 1024. Nur fängt man sich damit 
nicht schon vorher einen Rundungsfehler ein bevor es überhaupt losgeht? 
Genau wären es ja 711,111.

von Wolfgang (Gast)


Lesenswert?

Veit D. schrieb:
> Nur fängt man sich damit nicht schon vorher einen Rundungsfehler ein
> bevor es überhaupt losgeht?
> Genau wären es ja 711,111.

So ist das. Rundungsfehler fängst du dir selbst bei float oder double 
ein, weil du nur eine endliche Zahl von Bits zur Verfügung hast ;-)

von A. S. (Gast)


Lesenswert?

Veit D. schrieb:
> Nur fängt man sich damit nicht schon vorher einen Rundungsfehler ein
> bevor es überhaupt losgeht?

Bei nur wenigen Aufgaben ist das schlimm. Und noch seltener ist dieser 
Fehler größer als z.b. ein off-by-one beim Teiler oder Rundungsoffset.

Wichtig ist nur die Priorität:

Erst richtig (runden), dann lesbar. Und effizient nur, wenn man es sich 
erlauben kann oder muss. Wenn 2 zahlen im Datenblatt oder Schaltplan 
stehen, sind die einfacher zu verstehen als wenn da zwei ganz andere 
sind.

von Justin S. (Gast)


Lesenswert?

Uwe B. schrieb:
> M.e.a. rundet ((43*1000) + (1440/2))/1440 richtig

Ja, das ist die korrekte Antwort. Alles andere muss man nicht lesen

von Wolfgang (Gast)


Lesenswert?

Veit D. schrieb:
> Nur fängt man sich damit nicht schon vorher einen Rundungsfehler ein bevor
> es überhaupt losgeht?

Wenn dir der Rundungsfehler zu groß ist, musst du eine bessere Näherung 
für deine 1000/1440 verwenden.

Bei Annäherung durch 711/1024 weicht dein Faktor um 0,016 Prozent ab, 
bei 5689/8192 wäre es nur eine Abweichung von 0,002 Prozent, also ein 
Faktor 10 besser. Dafür schränkt sich bei gegebenem Variablentyp dein 
Zahlenbereich entsprechend ein.

von Rolf M. (rmagnus)


Lesenswert?

Wolfgang schrieb:
> Bei Annäherung durch 711/1024 weicht dein Faktor um 0,016 Prozent ab,
> bei 5689/8192 wäre es nur eine Abweichung von 0,002 Prozent, also ein
> Faktor 10 besser. Dafür schränkt sich bei gegebenem Variablentyp dein
> Zahlenbereich entsprechend ein.

Hier ist ausdrücklich ein Bereich bis 55 vorgegeben. Ob man den in 
diesem Fall halten kann, hängt davon ab, wie groß auf der Plattform int 
ist.

von Roland F. (rhf)


Lesenswert?

Hallo,
Jens schrieb:
> ich habe folgenes Problem:...

Es wurde in dieser Diskussion zwar schon diverse Male die Lösung 
beschrieben aber hier nochmal eine ausführliche Erklärung wie das mit 
dem Runden bei Ganzzahl-Arithmetik geht:

Beitrag "Re: UART Baudrate clever runden"

rhf

von WS (Gast)


Lesenswert?

Bis x=55:
(x*89+62)/128

1  0,69  1,00
2  1,39  1,00
3  2,08  2,00
4  2,78  3,00
5  3,47  3,00
6  4,17  4,00
7  4,86  5,00
8  5,56  6,00
9  6,25  6,00
10  6,94  7,00
11  7,64  8,00
12  8,33  8,00
13  9,03  9,00
14  9,72  10,00
15  10,42  10,00
16  11,11  11,00
17  11,81  12,00
18  12,50  13,00
19  13,19  13,00
20  13,89  14,00
21  14,58  15,00
22  15,28  15,00
23  15,97  16,00
24  16,67  17,00
25  17,36  17,00
26  18,06  18,00
27  18,75  19,00
28  19,44  19,00
29  20,14  20,00
30  20,83  21,00
31  21,53  22,00
32  22,22  22,00
33  22,92  23,00
34  23,61  24,00
35  24,31  24,00
36  25,00  25,00
37  25,69  26,00
38  26,39  26,00
39  27,08  27,00
40  27,78  28,00
41  28,47  28,00
42  29,17  29,00
43  29,86  30,00
44  30,56  31,00
45  31,25  31,00
46  31,94  32,00
47  32,64  33,00
48  33,33  33,00
49  34,03  34,00
50  34,72  35,00
51  35,42  35,00
52  36,11  36,00
53  36,81  37,00
54  37,50  38,00
55  38,19  38,00

WS

von W.S. (Gast)


Lesenswert?

Jens schrieb:
> Wie bekomme ich das genauer.

Entweder du entschließt dich doch zur Rechnung in float oder du benutzt 
die Funktion div bzw. ldiv. Die liefern neben dem Quotienten auch den 
Rest, den du dann auswerten kannst, um ggf. das Ergebnis nach der 
Truncation auf Integer um 1 zu erhöhen.

W.S.

von Kurt (Gast)


Lesenswert?

Du hast einen Faktor von 1000/1440 = 0,694444...
Ich würde mit 0,694444 x 65536 = 45511 multiplizieren.

16 Bit x 16 Bit -> 32 Bit

Wirft man die unteren 16 Bit weg, rundet aber die verbleibenden
16 Bit um 1 auf, wenn das höchste weggeworfene Bit gesetzt ist,
kommt man auf sehr gut gerundete Ergebnisse.

Mit asm kein Problem.
Aber auch mit C gut machbar.

Gute Nacht!

von WS (Gast)


Lesenswert?

(x<<6+x<<4+x<<3+x+62)>>7
Nix 16x16 und 32Bit und evtl. runden

Bis 55!!

von Rolf M. (rmagnus)


Lesenswert?

WS schrieb:
> (x<<6+x<<4+x<<3+x+62)>>7
> Nix 16x16 und 32Bit und evtl. runden

Allerdings auf einem AVR auch nicht sonderlich performant, mangels 
barrel shifter.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Für AVRs mit Multiplizierer:
1
erg = (test * 178 + 124) / 256

Das ist die weiter oben von W.S. vorgeschlagene Lösung mit doppelt so
großen Parametern, um den 7-Bit-Shift zu vermeiden.

Das funktioniert für 0 ≤ tmp ≤ 76 und braucht nur 7 Taktzyklen.

: Bearbeitet durch Moderator
von WS (Gast)


Lesenswert?

((x<<6+x<<4+x<<3+x+62)<<1)>>8
Gruß

WS

von M. K. (sylaina)


Lesenswert?

Dividiert durch 0x100 und nicht 0xff…wo ist mein Kaffee, ich sehs grad 
nicht…

von WS (Gast)


Lesenswert?

((((x<<2+x)<<1+x)<<3+x+62)<<1)>>8
2+1+3+1 shift left
4 add
Ohne mul
WS

von Peyer (Gast)


Lesenswert?

Yalu X. schrieb:
> Für AVRs mit Multiplizierer:
>
1
> erg = (test * 178 + 124) / 256
2
>

Sind die 124 ein Fehler (128) oder hat es damit was auf sich?

von WS (Gast)


Lesenswert?

Optimierung des Fehlers

von Franz (Gast)


Lesenswert?

Diese "Mul-Shift"-Vorgehensweise ist heute nicht mehr grundsätzlich zu 
empfehlen, da inzwischen öfters ein div direkt vom Controller 
unterstützt wird. Beim "Mul-Shift" müssen zum einen Überläufe verhindert 
werden und zum anderen könnte temporär ein größerer Datentyp nötig sein. 
Das alles wirkt sich negativ auf die Programmierzeit, Fehlergefahr und 
Laufzeit aus.

von HildeK (Gast)


Lesenswert?

Wir haben alle mit den vom TO genannten Zahlen eine spezielle Lösung 
genannt, die für ihn funktioniert.
Man sollte die Rechnung auch etwas allgemeiner betrachten. So wäre das 
korrekt gerundete Ergebnis der der Ganzzahldivision erg=a*b/c dann:
erg = (a*b + c/2)/c

Näherungen mit schnelleren Bitshifts sind nur bei festen Zahlen b und c 
zu finden und vermutlich nicht mal bei allen.

von WS (Gast)


Lesenswert?

Hallo,
1000/1440*128=88,88888.. = 89-1/9
Also
x*(89-1/9)+64 < x*89+64
Es kann also sein, dass (x×89+64)/128 als Ganzzahl im Argumentbereich zu 
gross ist. Deshalb versuche ich in der Rundung zu optimieren.
Das Ergebnis ist exakt im Argumentbereich und optimal umsetzbar in Zeit 
und Recourceneinsatz. Auch ohne mul und 32bit optimal zu lösen.

WS

von A. S. (Gast)


Lesenswert?

HildeK schrieb:
> Wir haben alle mit den vom TO genannten Zahlen eine spezielle Lösung
> genannt, die für ihn funktioniert.

Im Gegenteil: Die meisten haben das Problem des TO (die Rundung) 
ignoriert oder beiläufig eingeflochten und sich auf die (unnötige) 
Optimierung der Division gestürzt. Und manche haben gar das Float-Fass 
geöffnet.

Und das, obwohl der TO im OP alle Informationen gegeben hat und es im 
Titel sehr präzise zusammengefasst hat:

> richtig Runden nach Ganzzahl Operation

Eine spezielle Lösung wäre die Wertetabelle. Bei 55 Werten von 8 Bit ein 
überschaubarer Aufwand mit perfekter (beliebiger) Genauigkeit.

von Jürgen S. (engineer) Benutzerseite


Lesenswert?

auch wenn es länger her ist:
Beitrag "Re: Rauschleistung eines Netzwerks"

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.