Hallo,
ich habe ein kleines Problem:
Ich addiere zwei 16bit Werte. Wie erkenne ich in C ob der Wert >65535
ist, also die Variable überläuft ?
Folgendes funktioniert ja nicht:
if ((a+b)>65535)
da der uC ja nur mit 16bit Variablen rechnet und das Ergebnis somit nie
wahr ist. Es geht um eine Regelschleife für eine Spannungsregelung. Wenn
die Eingangsspannung aber unter der Ausgangsspannung liegt, regelt der
Controller immer weiter hoch bis der Wert überläuft. Er soll dann
einfach bei 65535 stehen bleiben.
PS: Es ist ein M16C Controller.
Schau mal in der Beschreibung zum M16c nach:
Flag register (FLG)
Bit 5: Overflow flag (O flag)
This flag is set to 1 when an arithmetic operation resulted in
overflow; otherwise, cleared to 0.
Das sollte dir bei deinem Problem helfen....
Gruß
Peter
Wenn man zwei positive Zahlen addiert und das Ergebnis ist kleiner als
die beiden Zahlen (jeweils einzeln) oder zwei negative Zahlen addiert
und das Ergebnis ist größer als die beiden Zahlen, dann hat ein
Überlauf stattgefunden. Addiert man eine positive und eine negative
Zahl, dann kann kein Überlauf stattfinden.
Also ungefähr so:
summe = a + b;
if ((a > 0) && (b > 0) && ((summe < a) || (summe < b))) { Überlauf }
if ((a < 0) && (b < 0) && ((summe > a) || (summe > b))) { Überlauf }
Markus
"...da der uC ja nur mit 16bit Variablen rechnet..."
Na und !
Das interressiert doch keinen.
long ist immer 32 bit und da kannst Du den Test bequem manchen oder
komplett in long rechnen.
Allerdings ist der Weg von Hans falsch, er macht erst die Addition und
wandelt danach auf 32 Bit, da ist aber der Überlauf schon
abgeschnitten.
Oder in float, wenns nur für ne lahme Ausgabe auf ein Display ist.
Dein Taschenrechner rechnet intern sogar nur 4-Bittig, genau wie ein
Mensch nicht nur bis 10 zählen kann obwohl er nur 10 Finger hat.
Peter
@Peter Kasi
Schön, es gibt ein Overflow Flag, aber wie komme ich in C dran ?
@peter dannegger
Dann werde ich wohl long verwenden. Float braucht mir zuviel CPU
Leistung.
Laut Peter sollte der Compiler die Bezeichnung FLG des Registers kennen
(sonst musst du wohl oder übel ins Datenblatt schauen und die Adresse
raussuchen), ergo ganz einfach wahrscheinlich
if( FLG & 0x20) --> überlauf.
Falls das Symbol FLG nicht bekannt ist, müsste so etwas wie
if( *(U16*)0xRegisterAdresse & 0x20) funktionieren.
Überlaufflag:
Alles in der Hoffnung, daß der Compiler nicht noch schnell einen
anderen (Asm)-Befehl zwischenreinschiebt, der das OvlFlag nicht wieder
manipuliert... -> unsaubere Lösung.
Ich schließe mich der Meinung "in long rechnen" an und gut is.
----, (QuadDash).
1. Der Compiler kennt FLG nicht, (sonst wäre es ja auch kein Problem
gewesen und ich hätte die Frage garnicht erst gestellt.)
2. Flag ist ein CPU Register und hat somit keine Adresse. Mann kann
dieses Register in C also nicht auslesen...
@peter dannegger:
Habe grade keinen K&R zur Hand, allerdings habe ich noch in
Erinnerung das die alle Operanden eines Ausdrucks auf den
"groessten" in dem Ausdruck vorkommenden Datentyps konvertiert
werden und danach auf den Ergebnistyp konvertiert werden.
Ggf. sollte bei 16 Bit Compiler die Konstante auf long gecastet
werden oder eine long Konstante (65535L) benutzt werden.
Habe lange nicht mehr auf <32 Bit Systemen programmiert :-()
Vieleicht kann jemand das Beispiel unten durch einen
vernueftigen 16Bit Compiler jagen und den
Assembler Output hier posten
Beispiel:
router:~# cat t.c
main()
{
unsigned short a,b;
long c;
a=40000;
b=40000;
c=a+b;
printf("res: %ld\n",c);
if (((long)(a+b)) > 65535) {
puts("true");
}
}
router:~# gcc -S t.c
router:~# cat t.s
.file "t.c"
.version "01.01"
gcc2_compiled.:
.section .rodata
.LC0:
.string "res: %ld\n"
.LC1:
.string "true"
.text
.align 4
.globl main
.type main,@function
main:
pushl %ebp
movl %esp,%ebp
subl $24,%esp
movw $40000,-2(%ebp)
movw $40000,-4(%ebp)
movzwl -2(%ebp),%eax
movzwl -4(%ebp),%edx
leal (%edx,%eax),%ecx
movl %ecx,-8(%ebp)
addl $-8,%esp
movl -8(%ebp),%eax
pushl %eax
pushl $.LC0
call printf
addl $16,%esp
movzwl -2(%ebp),%eax
movzwl -4(%ebp),%edx
addl %edx,%eax
cmpl $65535,%eax
jle .L3
addl $-12,%esp
pushl $.LC1
call puts
addl $16,%esp
.L3:
.L2:
leave
ret
.Lfe1:
.size main,.Lfe1-main
.ident "GCC: (GNU) 2.95.4 20011002 (Debian prerelease)"
router:~# ./t
res: 80000
true
router:~#
@hans
"alle Operanden eines Ausdrucks auf den
"groessten" in dem Ausdruck vorkommenden Datentyps konvertiert
werden und danach auf den Ergebnistyp konvertiert werden."
genau so isses !
Deshalb muß man zuerst a oder b nach long casten und *danach"
addieren:
if ((( (long)a +b)) > 65535) {
Wenn Dein Compiler aber eine int Addition in long ausführen sollte, so
ist das falsch.
Peter
@ Peter Dannegger:
Allerdings besteht der Ausdruck nicht nur aus "a+b" sondern
aus "a+b > 65535". Auch c > d ist ein Ausdruck und deshalb besteht
der Ausdruck aus den Operanden "a","b" und "65535" bzw. aus
"(( (long)a +b))" und 65535. Man muesste ueberpruefen ob es
spezielle Regeln fuer Teilausdruecke gibt, IMHO kann ich mir
sowas schlecht vorstellen.
Aber egal, ich werde am Wochenende mal nachsehen, allerdings
halte ich den gcc nicht fuer den schlechtesten Compiler und halte
es fuer unwahrscheinlich das deratiges falsch implementiert
worden ist.
Leider habe ich auch keine weiteren Compiler an der Hand mit denen
ich das testen kann. Ggf. hast du ja andere zur Hand von denen
man sich den Assembler Output ansehen kann, Ergebnisse bitte hier
posten, denn langsam werde ich neugierig.
In C gibt es keine Ausdrücke und Teilausdrücke.
Ein Ausdruck ist grob gesagt immer das, was man nicht weiter
unterteilen kann.
Du kannst eine beliebige Anzahl an Ausdrücken in eine Zeile schreiben,
an der Art, wie sie ausgeführt werden, ändert das nichts (wäre sonst ja
schlimm).
Wenn ich exakt Deine Vergleichszeile in den AVR-GCC eingebe:
char test( unsigned short a, unsigned short b ){
if (((long)(a+b)) > 65535)
return 0;
return 1;
}
kriege ich das hier:
MAIN.C: In function `test':
MAIN.C:4: warning: comparison is always false due to limited range of
data type
Und das Codelisting sieht dann so aus:
0000005c <test>:
char test( unsigned short a, unsigned short b ){
if (((long)(a+b)) > 65535)
return 0;
return 1;
}
5c: 81 e0 ldi r24, 0x01 ; 1
5e: 90 e0 ldi r25, 0x00 ; 0
60: 08 95 ret
Er hat also den Vergleich komplett wegoptimiert.
Und damit hat er recht.
Peter