[gelöst] wie prüft fpc einen Zahlenbereich bei Case?

Für Fragen von Einsteigern und Programmieranfängern...
Antworten
NoCee
Beiträge: 170
Registriert: Do 3. Mär 2011, 21:34
OS, Lazarus, FPC: WinXp/7/10 Opensuse13.2/Leap15.3 (L 2.2.0 FPC 3.2.2 )
CPU-Target: Intel 32/64Bit, ARM9
Wohnort: Ulm

[gelöst] wie prüft fpc einen Zahlenbereich bei Case?

Beitrag von NoCee »

Hallo zusammen,
ich habe mir bei einem Programm überlegt eine Case Anweisung zu verwenden die aber einen Zahlenbereich überprüfen soll.
Also in der Art:

Code: Alles auswählen

case myvar of
  0..9: writeln('Einstellig');
  10..99: writeln('Zweistellig');
  100..999: writeln('Dreistellig');
  end;//case
  
Was passiert wenn ich eine 64bit Variable habe und Case einen riesigen Bereich prüfen muß?
Probiert da Case Wert für Wert durch?
Wären ja im worst case ewig viele Durchläufe oder hat fpc da einen cleveren Trick drauf?

Für eine Erhellung wäre ich dankbar.
Gruß
NoCee
Zuletzt geändert von NoCee am Do 15. Jun 2023, 17:41, insgesamt 1-mal geändert.

Benutzeravatar
corpsman
Lazarusforum e. V.
Beiträge: 1498
Registriert: Sa 28. Feb 2009, 08:54
OS, Lazarus, FPC: Linux Mint Mate, Lazarus GIT Head, FPC 3.0
CPU-Target: 64Bit
Wohnort: Stuttgart
Kontaktdaten:

Re: wie prüft fpc einen Zahlenbereich bei Case?

Beitrag von corpsman »

ich würde erwarten dass der das übersetzt in

Code: Alles auswählen

if (myvar >= 0) and  (myvar <= 9) then begin .. end
else if (myvar >= 10) and  (myvar <= 99) then begin .. end
else if (myvar >= 100) and  (myvar <= 999) then begin .. end
aber im zweifel kannst sicher im asm nach sehen ...
--
Just try it

Warf
Beiträge: 1913
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: wie prüft fpc einen Zahlenbereich bei Case?

Beitrag von Warf »

Probiers doch einfach mal aus: https://godbolt.org/z/rKE6nnaW8

Code: Alles auswählen

procedure test(i: Integer);
begin
  case i of
  0..9: Foo;
  10..99: Bar;
  100..999: Baz;
  end;//case
end;
-O1:

Code: Alles auswählen

test(longint):
        pushq   %rbp
        movq    %rsp,%rbp
        leaq    -16(%rsp),%rsp
        movl    %edi,-8(%rbp)
        movl    %edi,%eax
        testl   %edi,%edi
        jl      .Lj12
        subl    $9,%eax
        jle     .Lj13
        subl    $90,%eax
        jle     .Lj14
        subl    $900,%eax
        jle     .Lj15
        jmp     .Lj12
.Lj13:
        call    foo()
        jmp     .Lj11
.Lj14:
        call    bar()
        jmp     .Lj11
.Lj15:
        call    baz()
.Lj12:
.Lj11:
        movq    %rbp,%rsp
        popq    %rbp
        ret
Subl ist Subtraktion und jle ist jump lesser equal. Das ist im grunde also:

Code: Alles auswählen

if i < 0 then
  Exit;

i -= 9;
if i <= 0 then
begin
  Foo;
  Exit;
end;

i -= 90;
if i <= 0 then
  Bar;
  Exit;
end;

i -= 900;
if i <= 0 then
  Baz;
  Exit;
end;

Exit;
Mit -O3 ist das das ein bisschen "aufgeräumter" aber im grunde das selbe:

Code: Alles auswählen

test(longint):
        leaq    -8(%rsp),%rsp
        movl    %edi,%eax
        testl   %edi,%edi
        jl      .Lj12
        subl    $9,%eax
        jle     .Lj13
        subl    $90,%eax
        jle     .Lj14
        subl    $900,%eax
        jle     .Lj15
        jmp     .Lj12
.Lj13:
        call    foo()
        jmp     .Lj11
.Lj14:
        call    bar()
        jmp     .Lj11
.Lj15:
        call    baz()
.Lj12:
.Lj11:
        leaq    8(%rsp),%rsp
        ret
Tatsächlich ist einer der Gründe warum man Case...of vor If-then-else bevorzugen sollte, das es dem compiler die Freiheit lässt code genau so zu übersetzen und zu optimieren. Deshalb sorgt Case..Of meist für effizienteren code als if-then-else
Zuletzt geändert von Warf am Mi 14. Jun 2023, 20:01, insgesamt 2-mal geändert.

Warf
Beiträge: 1913
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: wie prüft fpc einen Zahlenbereich bei Case?

Beitrag von Warf »

Ich fühle mich grade so als sollte ich das mit dem case..of besser als if-then-else mit einem beispiel belegen, hier ist der selbe code als if-then-else:

Code: Alles auswählen

procedure test(i: Integer);
begin
  if (i >= 0) and (i <= 9) then
    Foo
  else if (i >= 10) and (i <= 99) then
    Bar
  else if (i >= 100) and (i <= 999) then
    Baz;
end;
Mit O3:

Code: Alles auswählen

test(longint):
        pushq   %rbx
        movl    %edi,%ebx
        testl   %ebx,%ebx
        jnge    .Lj12
        cmpl    $9,%ebx
        jnle    .Lj12
        call    foo()
        jmp     .Lj14
.Lj12:
        cmpl    $10,%ebx
        jnge    .Lj16
        cmpl    $99,%ebx
        jnle    .Lj16
        call    bar()
        jmp     .Lj18
.Lj16:
        cmpl    $100,%ebx
        jnge    .Lj20
        cmpl    $999,%ebx
        jnle    .Lj20
        call    baz()
.Lj20:
.Lj18:
.Lj14:
        popq    %rbx
        ret
Wie hier zu sehen ist wird statt sehr effizienten subtraktionen, komplexere Compare operationen ausgeführt, außerdem (wegen short circuit boolschen operatoren) werden viel mehr compares und jumps ausgeführt. Also statt statt 4 subtract + jump lesser (equals) wie vorher sind es jetzt 7 compare + jump kombinationen. Also doppelt so viele instruktionen und die instruktionen die gemacht werden sind noch dazu teurer

NoCee
Beiträge: 170
Registriert: Do 3. Mär 2011, 21:34
OS, Lazarus, FPC: WinXp/7/10 Opensuse13.2/Leap15.3 (L 2.2.0 FPC 3.2.2 )
CPU-Target: Intel 32/64Bit, ARM9
Wohnort: Ulm

Re: [gelöst] wie prüft fpc einen Zahlenbereich bei Case?

Beitrag von NoCee »

Au Mann,
da hab ich zu viele Bäume vor dem Wald gehabt. :oops:
Ich hab da immer nur einen Vergleich auf Gleichheit für jedes Element gesehen und das wäre ja irre.
Ein Gedanke an größer oder kleiner Vergleich ist mir nicht gekommen.
Also hätte mir der Hinweis auf <,> schon gereicht.

vielen Dank für die ausführlichen Antworten

Gruß
NoCee

Mathias
Beiträge: 6209
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: [gelöst] wie prüft fpc einen Zahlenbereich bei Case?

Beitrag von Mathias »

Einzig was schade ist, das "case " nur mit Konstanten funktioniert.
Ein case-Block sieht einiges sauberer aus, als ein if else gewurstel und ist auch leserlicher und weniger Fehleranfällig.
Sowas sieht einfach doof aus:

Code: Alles auswählen

var
  a:word=7;
begin  
    if a = 1 then begin
      WriteLn('1');
    end else if (a > 2) and (a < 9) then begin
      WriteLn('2..9');
    end else if a = 10 then begin
      WriteLn('10');
    end else begin
      WriteLn('>10');
    end;  
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Antworten