Switch-Case mit Zahlenspannen (x-y)

Gast #2508494
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
Gast #2508523
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.
Gast #2508524
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...
#2508584
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).
#2508619
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.
#2508953
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.
Persönliche Seite #2509412
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.
Persönliche Seite #2509449
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) :-))

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren