Forum: Compiler & IDEs Switch-Case mit Zahlenspannen (x-y)


von Ohmisch (Gast)


Lesenswert?

Hi.

Ich lasse eine Variable mit einem Interrupt hochzählen.
Dann möchte ich, wenn die Variable zwischen 0-100 liegt eine Funktion 
aufrufen, wenn sie zwischen 101-200 liegt eine andrer Funktion aufrufen 
usw.
Ich könnte es mit if-Vergleichen programmieren.
Aber dann wird es schnell unübersichtlich.
Kann man das auch ohne if mit Switch-Case programmieren?
Wenn ja wie?

Vielen Dank im Vorraus

von Besserwisser (Gast)


Lesenswert?

Nein!

von SF (Gast)


Lesenswert?

Es gibt eine Erweiterung vom GCC die folgendes ermöglicht:
1
switch(x){
2
  case 0 ... 100:
3
    EineFunktion(); break;
4
  case 101 ... 200:
5
    AndereFunktion(); break;
6
  default:
7
    UupsFunktion();
8
}

von adsedgfhhj (Gast)


Lesenswert?

Ohmisch schrieb:
> Hi.
>
> Ich lasse eine Variable mit einem Interrupt hochzählen.
> Dann möchte ich, wenn die Variable zwischen 0-100 liegt eine Funktion
> aufrufen, wenn sie zwischen 101-200 liegt eine andrer Funktion aufrufen
> usw.
> Ich könnte es mit if-Vergleichen programmieren.
> Aber dann wird es schnell unübersichtlich.
Was ist daran
1
if(var>=0 && var <=100)
2
  func1();
3
else if(var>=101 && var <=200)
4
  func2();
unübersichtlich?

> Kann man das auch ohne if mit Switch-Case programmieren?
Nein. Naja, man könnte case 1: case 2: case ...n: func1(); break; 
schreiben, das wäre wirklich unübersichtlich.

von adsedgfhhj (Gast)


Lesenswert?

SF schrieb:
> Es gibt eine Erweiterung vom GCC die folgendes ermöglicht:
>
1
> switch(x){
2
>   case 0 ... 100:
3
>     EineFunktion(); break;
4
>   case 101 ... 200:
5
>     AndereFunktion(); break;
6
>   default:
7
>     UupsFunktion();
8
> }
9
>

Ok, ich ziehe meine Aussage zurück und behaupte das Gegenteil...

von Ohmisch (Gast)


Lesenswert?

Alles klar ich wollte nur wissen ob so was möglich wär.
Dann mach ich es mit if.
Vielen Dank

von Klaus W. (mfgkw)


Lesenswert?

1
switch( (i-1)/100 )
2
{
3
case 0: // 0...100
4
        ...
5
        break;
6
case 1: // 101...200
7
        ...
8
        break;
9
...

von Peter D. (peda)


Lesenswert?

@Klaus Wachtler

Da freut sich aber die CPU, daß sie richtig schön Zyklen verbraten darf 
für die Division.


Peter

von Peter D. (peda)


Lesenswert?

Wenn man den GCC nimmt, sind die Bereiche besser lesbar gegenüber dem 
"if" und er optimiert auch deutlich besser.


Peter

von Klaus W. (mfgkw)


Lesenswert?

Solange man beim gcc bleibt, ist das sicher besser - wie so vieles beim 
gcc.

Zum Thema Rechenzeit: es kommt natürlich deauf, was alles in den Zweigen 
passiert und ob die Entscheidung ium switch überhaupt ins Gewicht fällt.
Natürlich kostet eine Division Arbeit, aber umsonst sind die anderen 
Lösungen auch nicht (Sprungtabellen bspw. kosten Platz).

von Mark B. (markbrandis)


Lesenswert?

Peter Dannegger schrieb:
> Da freut sich aber die CPU, daß sie richtig schön Zyklen verbraten darf
> für die Division.

Könnte ja auch auf nem PC sein, auf dem sich die CPU oft langweilt ;-)

von Karl H. (kbuchegg)


Lesenswert?

Ohmisch schrieb:
> Alles klar ich wollte nur wissen ob so was möglich wär.
> Dann mach ich es mit if.

Aber mach die Bereichsabfragen ein bißchen intelligent.

eine unsigned Zahl kann zb schon mal grundsätzlich nicht kleiner als 0 
sein. Wenn besagte Zahl dann nicht beispielsweise im Bereich 0 bis 100 
liegt, dann brauchst du in der 2.ten Abfrage auch nicht mehr testen, ob 
sie größer als 100 ist. Sie muss es sein, sonst wärst du nicht in diesem 
Fall gelandet.

Also nicht
1
if(var>=0 && var <=100)
2
  func1();
3
else if(var>=101 && var <=200)
4
  func2();

sondern
1
if( var <=100 )
2
  func1();
3
else if( var <= 200 )
4
  func2();

liefert genau die gleiche Logik, spart aber unnötige Vergleiche ein. 
Wenn Abfragebereiche aneinanderstossen, geht immer was. Frei nach dem 
Motto: Wenn etwas in einem Bereich ist, kann es nicht mehr im anderen 
Bereich sein.

von Εrnst B. (ernst)


Lesenswert?

Karl Heinz Buchegger schrieb:
> liefert genau die gleiche Logik, spart aber unnötige Vergleiche ein.
> Wenn Abfragebereiche aneinanderstossen, geht immer was.

Würde mich aber nicht wundern, wenn der Compiler genau dasselbe selbst 
herausfindet.
Mag jemand mal den ASM-Output für beide Varianten vergleichen?

von Karl H. (kbuchegg)


Lesenswert?

OK.
Mein WinAVr auf dieser Maschine hier ist von der älteren Sorte
WinAVR20090313

Optimierung ist auf -Os
Schaun wir mal
1
#include <avr/io.h>
2
3
int main()
4
{
5
  uint8_t i;
6
  uint8_t j;
7
8
  i = PINB;
9
10
  if( i < 100 )
11
    j = 80;
12
  else if( i >= 100 && i < 200 )
13
    j = 100;
14
  else
15
    j = 120;
16
17
  PORTD = j;
18
}

daraus wird
1
  i = PINB;
2
  6c:  86 b3         in  r24, 0x16  ; 22
3
4
  if( i < 100 )
5
  6e:  84 36         cpi  r24, 0x64  ; 100
6
  70:  10 f4         brcc  .+4        ; 0x76 <main+0xa>
7
  72:  80 e5         ldi  r24, 0x50  ; 80
8
  74:  06 c0         rjmp  .+12       ; 0x82 <main+0x16>
9
    j = 80;
10
  else if( i >= 100 && i < 200 )
11
  76:  84 56         subi  r24, 0x64  ; 100
12
  78:  84 36         cpi  r24, 0x64  ; 100
13
  7a:  10 f0         brcs  .+4        ; 0x80 <main+0x14>
14
  7c:  88 e7         ldi  r24, 0x78  ; 120
15
  7e:  01 c0         rjmp  .+2        ; 0x82 <main+0x16>
16
  80:  84 e6         ldi  r24, 0x64  ; 100
17
    j = 100;
18
  else
19
    j = 120;
20
21
  PORTD = j;
22
  82:  82 bb         out  0x12, r24  ; 18

Nope. Er hats nicht gefunden. Ist aber trotzdem interssant, wie der 
Compiler das gemacht hat. Ich hätte noch einen else-if Fall anhängen 
sollen :-)

Gegentest
1
#include <avr/io.h>
2
3
int main()
4
{
5
  uint8_t i;
6
  uint8_t j;
7
8
  i = PINB;
9
10
  if( i < 100 )
11
    j = 80;
12
  else if( i < 200 )
13
    j = 100;
14
  else
15
    j = 120;
16
17
  PORTD = j;
18
}

wird zu
1
  i = PINB;
2
  6c:  86 b3         in  r24, 0x16  ; 22
3
4
  if( i < 100 )
5
  6e:  84 36         cpi  r24, 0x64  ; 100
6
  70:  10 f4         brcc  .+4        ; 0x76 <main+0xa>
7
  72:  80 e5         ldi  r24, 0x50  ; 80
8
  74:  05 c0         rjmp  .+10       ; 0x80 <main+0x14>
9
    j = 80;
10
  else if( i < 200 )
11
  76:  88 3c         cpi  r24, 0xC8  ; 200
12
  78:  10 f0         brcs  .+4        ; 0x7e <main+0x12>
13
  7a:  88 e7         ldi  r24, 0x78  ; 120
14
  7c:  01 c0         rjmp  .+2        ; 0x80 <main+0x14>
15
  7e:  84 e6         ldi  r24, 0x64  ; 100
16
    j = 100;
17
  else
18
    j = 120;
19
20
  PORTD = j;
21
  80:  82 bb         out  0x12, r24  ; 18


Aber ganz davon abgesehen, ist es ja auch fehleranfällig, wenn man 
dieselbe Grenze im Code 2-mal angibt. Denn dann muss man selbst dafür 
sorgen, dass beide Werte immer konsistent bleiben.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Peter Dannegger schrieb:
> @Klaus Wachtler
>
> Da freut sich aber die CPU, daß sie richtig schön Zyklen verbraten darf
> für die Division.

Hängt von der Optimierung ab
1
void select (unsigned char i)
2
{
3
    extern void func_1 (void);
4
    extern void func_2 (void);
5
    
6
    i--;
7
    i /= 100;
8
    
9
    if (i == 0)
10
        func_1();
11
    if (i == 1)
12
        func_2();
13
}
Allerdings beachte man, daß hier nicht (i-1)/100 steht, was eine 16-Bit 
Division bedeuten würde.

avr-gcc -mmcu=atmega8 -O2 schafft daraus:
1
select:
2
  subi r24,lo8(-(-1))
3
  ldi r25,lo8(41)
4
  mul r24,r25
5
  mov r24,r1
6
  clr __zero_reg__
7
  swap r24
8
  andi r24,lo8(15)
9
  breq .L5
10
  cpi r24,lo8(1)
11
  breq .L6
12
  ret
13
.L5:
14
  rjmp func_1
15
.L6:
16
  rjmp func_2

Entfernt man dann noch das "char" so daß i vom Typ unsigned ist und man 
es alse mit einer 16-Bit Division zu tun hat, ergibt sich
1
select:
2
  movw r18,r24
3
  subi r18,1
4
  sbc r19,__zero_reg__
5
  lsr r19
6
  ror r18
7
  lsr r19
8
  ror r18
9
  ldi r26,lo8(123)
10
  ldi r27,lo8(20)
11
  rcall __umulhisi3
12
  lsr r25
13
  ror r24
14
  sbiw r24,0
15
  breq .L5
16
  sbiw r24,1
17
  breq .L6
18
  ret
19
.L5:
20
  rjmp func_1
21
.L6:
22
  rjmp func_2

D.h. auch hier wird keine Division ausgeführt, zumindest keine über 
Schuldivision sondern über erweiternde Multipliketion mit dem 
Reziproken.

In Beiden Fällen sehe ich nicht wirklich Raum für Optimierung.

...wie auch immer... wenn man auf eine Division verzichten kann wie im 
vorliegenden Fall, dann sollte man es tun und if-else verwenden oder 
switch-case.

Für das Beispiel ist das Frage des Geschmacks, der erzeugte Code wird eh 
fast gleich sein.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Karl Heinz Buchegger schrieb:
> OK.
> Mein WinAVr auf dieser Maschine hier ist von der älteren Sorte
> WinAVR20090313
>
> Optimierung ist auf -Os

Ich hab's mal durch future 4.7 durchgelassen. Der Code sieht gleich aus 
(die Funktion heisst jetzt foo und gibt void zurück):
1
foo:
2
  in r24,0x3
3
  cpi r24,lo8(100)
4
  brlo .L3
5
  subi r24,lo8(-(-100))
6
  cpi r24,lo8(100)
7
  brsh .L4
8
  ldi r24,lo8(100)
9
  rjmp .L2
10
.L3:
11
  ldi r24,lo8(80)
12
  rjmp .L2
13
.L4:
14
  ldi r24,lo8(120)
15
.L2:
16
  out 0xb,r24
17
  ret

Was neues lässt sich avr-gcc erst einfallen, wenn man Sprünge teurer 
macht: zusätzlich mit -mbranch-cost=2 wird's 2 Instruktionen kürzer:
1
foo:
2
  in r25,0x3
3
  ldi r24,lo8(80)
4
  cpi r25,lo8(100)
5
  brlo .L2
6
  subi r25,lo8(-(-100))
7
  ldi r24,lo8(120)
8
  cpi r25,lo8(100)
9
  brsh .L2
10
  ldi r24,lo8(100)
11
.L2:
12
  out 0xb,r24
13
  ret
Übrigens ist avr-gcc 4.7 die erste 4-er Version, die es schafft, für 
mein Beispielprojekt kleineren Code zu erzeugen als 3.4.6 
(WinAVR-20060421) :-))

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.