Kapitel 13: Datentypen umwandeln

Inhaltsverzeichnis

Kapitel 15: Stringmanipulation

14. Verarbeitung von Zahlen

Die folgenden Kapitel werden ausführlicher auf die Möglichkeiten eingehen, vorhandene Daten zu verarbeiten. Für die Zahlendatentypen kommen dazu natürlich erst einmal eine Reihe eingebauter mathematischer Funktionen in Betracht, aber wir wollen auch auf den Umgang mit verschiedenen Zahlensystemen sowie auf das direkte Lesen oder Setzen einzelner Bit eingehen.

14.1 Mathematische Funktionen

14.1.1 Quadratwurzel, Absolutbetrag und Vorzeichen

Neben den Grundrechenarten +, -, *, /, \ und ^ gibt es noch weitere Standardfunktionen der Mathematik. Eine davon ist die Quadratwurzel, auf englisch square route, woraus sich der Funktionsname SQR() ableitet. SQR(n) ist diejenige nichtnegative Zahl, die mit sich selbst multipliziert n ergibt. Die Quadratwurzel einer negativen Zahl ist nicht definiert, da kein Quadrat negativ sein kann.1 Wenn Sie versuchen, die Quadratwurzel aus einer negativen Zahl zu ziehen, liefert FreeBASIC den Rückgabewert nan (not a number) oder auch -nan.

Warning Achtung:
nan (not a number) und inf (infinite, unendlich) sind besondere Speicherzustände bei Gleitkommazahlen, die für Ganzzahlen nicht existieren. Wenn Sie das Ergebnis einer nicht berechenbaren Aufgabe wie SQR(-1) in einer Ganzzahl-Variablen zu speichern versuchen, werden Sie unsinnige Ergebnisse erhalten.

Der absolute Betrag einer Zahl n ist ihr Abstand vom Wert 0, das bedeutet: Bei nichtnegativen Zahlen ist der Betrag der Zahl gleich der Zahl selbst. Bei negativen Zahlen fällt durch die Betragsbildung lediglich das negative Vorzeichen fort. Die Funktion wird in FreeBASIC mit ABS() aufgerufen.

Eng damit verbunden ist die Vorzeichenfunktion SGN(), vom lateinischen signum (Zeichen). Auch im Deutschen wird sie häufig als Signumfunktion bezeichnet. Sie gibt für positive Zahlen den Wert 1 zurück, für negative Zahlen den Wert -1 und für 0 den Wert 0.

Quelltext 14.1: Quadratwurzel; Betragsfunktion; Signumfunktion
PRINT " n        SQR(n)    ABS(n)    SGN(n)"
PRINT "===================================="

PRINT 9;    TAB(10); SQR(9);    TAB(20); ABS(9);    TAB(30); SGN(9)
PRINT 1.44; TAB(10); SQR(1.44); TAB(20); ABS(1.44); TAB(30); SGN(1.44)
PRINT -3;   TAB(10); SQR(-3);   TAB(20); ABS(-3);   TAB(30); SGN(-3)
PRINT 0;    TAB(10); SQR(0);    TAB(20); ABS(0);    TAB(30); SGN(0)
SLEEP
Ausgabe:
 n        SQR(n)    ABS(n)    SGN(n)
====================================
 9        3         9         1
 1.44     1.2       1.44      1
-3       -nan       3        -1
 0        0         0         0

14.1.2 Winkelfunktionen

Auch die Winkelfunktionen SIN() (Sinus), COS() (Kosinus) und TAN() (Tangens) sind im Standard-Befehlssatz enthalten. Den mathematischen Hintergrund dieser Funktionen hier zu beleuchten, würde deutlich zu weit führen. Für diejenigen, welche mit Winkelfunktionen bisher noch nichts zu tun hatten, sei vereinfacht zusammengefasst: Diese Funktionen ermöglichen in einem rechtwinkligen Dreieck, einen direkten Zusammenhang zwischen den Winkelgrößen und den Seitenverhältnissen herzustellen. Etwas weiter gefasst eignen sie sich z. B. hervorragend für Berechnungen in einem kartesischen Koordinatensystem.

FreeBASIC rechnet ausschließlich in Bogenmaß, d. h. ein Vollwinkel beträgt 2π. Wenn Sie in Gradmaß rechnen wollen, müssen Sie die Werte vorher umrechnen.

Quelltext 14.2: Trigonometrische Berechnungen
CONST PI AS DOUBLE = 3.141592653589793
DIM AS DOUBLE deg, rad

INPUT "Bitte geben Sie einen Winkel in Grad (DEG) ein: ", deg
rad = deg * PI / 180   ' Grad in Bogenmass umrechnen
PRINT ""
PRINT "SIN(" & deg & " Grad) = " & SIN(rad)
PRINT "COS(" & deg & " Grad) = " & COS(rad)
PRINT "TAN(" & deg & " Grad) = " & TAN(rad)
SLEEP
Ausgabe:
Bitte geben Sie einen Winkel in Grad (DEG) ein: 60

SIN(60 Grad) =  0.8660254037844386
COS(60 Grad) =  0.5000000000000001
TAN(60 Grad) =  1.732050807568877

Beachten Sie in diesem Beispiel, dass es beim Umgang mit Gleitkommazahlen unvermeidlich zu Rundungs-Ungenauigkeiten kommt.

Die Umkehrfunktionen, also der Arkussinus, Arkuskosinus und Arkustangens, werden über die Befehle ASIN(), ACOS() und ATN() aufgerufen. ASIN(x) gibt eine Zahl zurück, deren Sinus x ergibt. Da die Winkelfunktionen periodisch sind, ist die Umkehrung nicht eindeutig festgelegt. ASIN() und ATN() liefern den Wert, der im Bereich von -π/2 bis π/2 liegt, während ACOS() einen Wert im Bereich von 0 bis π zurückgibt.

Für ATN() gibt es noch den Alternativbefehl ATAN2(), dem zwei Parameter übergeben werden und der den Arkustangens des Quotienten beider Parameter zurückgibt. Ein entscheidender Unterschied zu ATN() ist, dass die Vorzeichen beider Parameter bei der Berechnung berücksichtigt werden: ATAN2(2, 3) ist nicht dasselbe wie ATAN2(-2, -3).

Quelltext 14.3: Arkusfunktionen
CONST PI AS DOUBLE = 3.141592653589793

PRINT "Einige Arkus-Berechnungen:"
PRINT "ASIN(0.5) = " & ASIN(.5) & " (" & ASIN(.5)/PI*180 & " Grad)"
PRINT "ACOS(0.5) = " & ACOS(.5) & " (" & ACOS(.5)/PI*180 & " Grad)"
PRINT

PRINT "ATAN2( 7,  24) = " & ATAN2(7, 24)
' ergibt das gleiche wie
PRINT "ATN ( 7 /  24) = " & ATN(7 / 24)
' aber etwas anderes als
PRINT "ATAN2(-7, -24) = " & ATAN2(-7, -24)   ' = ATAN2(7, 24) - PI
SLEEP
Ausgabe:
Einige Arkus-Berechnungen:
ASIN(0.5) = 0.5235987755982989 (30 Grad)
ACOS(0.5) = 1.047197551196598 (60.00000000000001 Grad)

ATAN2( 7,  24) = 0.2837941092083279
ATN ( 7 /  24) = 0.2837941092083279
ATAN2(-7, -24) = -2.857798544381466

ATAN(-7, -24) ist der um π „verschobene“ Wert von ATAN(7, 24) (also entsprechend einer Verschiebung um 180°). Der Tangens beider Werte ist gleich.

14.1.3 Exponentialfunktion und Logarithmus

EXP() ruft die Exponentialfunktion zur Basis e auf (die Eulersche Zahl e≈2.718). Die Umkehrfunktion dazu ist der natürliche Logarithmus, der in FreeBASIC über den Befehl LOG() angesprochen wird. Während mittels a^x die Potenz zu jeder beliebigen Basis direkt berechnet werden kann, gibt es keine vorgefertigte Funktion, die unmittelbar den Logarithmus zur Basis a zurück gibt. Hier bieten jedoch die Logarithmusgesetze eine schnelle und unkomplizierte Lösung.

Quelltext 14.4: Exponentialberechnungen
PRINT "Eulersche Zahl e ="; EXP(1)
PRINT "e^2     ="; EXP(2)
PRINT "LN(e^2) ="; LOG(EXP(2))
PRINT
PRINT "5 ^ 3   ="; 5^3
PRINT "Logarithmus von 125 zur Basis 5:"; LOG(125)/LOG(5)
SLEEP
Ausgabe:
Eulersche Zahl e = 2.718281828459045
e^2     = 7.38905609893065
LN(e^2) = 2

5 ^ 3   = 125
Logarithmus von 125 zur Basis 5: 3

14.1.4 Rundung

Zum Runden eines Zahlenwertes gibt es verschiedene Konzepte, je nachdem, was Sie erreichen wollen. Der einfachste Fall ist das Abrunden auf die nächstkleinere Ganzzahl (sofern es sich bei der Eingabe nicht schon um eine Ganzzahl handelt). Hierzu wird INT() verwendet. Etwas Ähnliches, jedoch nicht völlig Gleiches, macht FIX(): hier wird der Nachkomma-Anteil abgeschnitten. Der Unterschied beider Befehle liegt in der Behandlung negativer Zahlen. Während INT() immer abrundet, rundet FIX() immer „in Richtung 0“; negative Zahlen werden also aufgerundet.

Wenn Sie, wie man das üblicherweise gewohnt ist, bis .5 ab- und ab .5 aufrunden wollen, können Sie es mit CINT() versuchen. Sie erinnern sich: CINT(x) ist eine Kurzform von CAST(INTEGER, x), also eine Umwandlung zwischen zwei Datentypen. Wird eine Gleitkommazahl in eine Ganzzahl umgewandelt, muss sie natürlich gerundet werden.

Aber: Wie viele andere Programmiersprachen auch, rundet FreeBASIC nicht kaufmännisch, sondern mathematisch! Das bedeutet, dass bei .5 zur nächsten geraden Zahl gerundet wird — also z. B. bei 1.5 aufwärts auf 2, bei 4.5 aber abwärts auf 4. Hintergrund ist die bessere Verwertbarkeit gerundeter Werte für statistische Aufgaben, aber für ein Buchhaltungsprogramm wirft dieses Vorgehen Probleme auf.2

Glücklicherweise gibt es mit ein wenig Einfallsreichtum auch dazu eine Lösung. Wenn Sie zu einer Zahl x die „Hälfte des Vorzeichens“ addieren und anschließend mit FIX() die Nachkommastellen abschneiden, erhalten Sie eine kaufmännische Rundung. Hatte ein positives x einen Nachkomma-Anteil, der kleiner war als .5, dann erreichen Sie durch die Addition von 0.5 noch nicht die nächste ganze Zahl; x wird also abgerundet. Ab einem Nachkommaanteil von (einschließlich) .5 landen Sie durch die Addition im Bereich der nächsten Ganzzahl. Überlegen Sie, warum das beschriebene Verfahren auch für negative Zahlen funktioniert.

Quelltext 14.5 gibt einen Überblick über die vier vorgestellten Möglichkeiten:

Quelltext 14.5: Rundungsverfahren
PRINT " x        INT(x)    FIX(x)    CINT(x)   FIX(x+SGN(x)/2)"
PRINT "======================================================="
DIM AS DOUBLE wert(...) = { 1.2, 1.8, -1.8, 2.5, 3.5, -3.5 }

FOR i AS INTEGER = 0 TO UBOUND(wert)
  PRINT wert(i); TAB(12); INT(wert(i)); TAB(22); FIX(wert(i));
  PRINT TAB(32); CINT(wert(i)); TAB(42); FIX(wert(i)+SGN(wert(i))/2)
NEXT
SLEEP
Ausgabe:
 x        INT(x)    FIX(x)    CINT(x)   FIX(x+SGN(x)/2)
=======================================================
 1.2        1         1         1         1
 1.8        1         1         2         2
-1.8       -2        -1        -2        -2
 2.5        2         2         2         3
 3.5        3         3         4         4
-3.5       -4        -3        -4        -4

Beachten Sie den Unterschied zwischen INT(-1.8) und FIX(-1.8) sowie die Besonderheit bei CINT(2.5). Selbstverständlich können Sie das Array in Zeile 3 um eigene Werte ergänzen, um die Rundungsverfahren weiter zu erforschen.

Wenn Sie umgekehrt nur den Nachkommateil einer Zahl erhalten wollen, können Sie FRAC() verwenden. Das Vorzeichen der Zahl bleibt dabei erhalten, d. h. FRAC(3.14) gibt den Wert 0.14 zurück und FRAC(-3.14) den Wert -0.14.

14.1.5 Modulo-Berechnung

Während die Integerdivision \ den ganzzahligen Anteil einer Division zweier Integer zurückgibt, bestimmt die Modulo-Berechnung MOD den Rest, der bei dieser Division bleibt. 7 MOD 3 z. B. ist der Rest der Division 7/3, also 1. Ist die erste Zahl negativ, dann bleibt das Vorzeichen erhalten, d. h. -7 MOD 3 ist -1. Das Vorzeichen der zweiten Zahl spielt dagegen keine Rolle.

Wenn Sie die Modulo-Berechnung bei Gleitkommazahlen anwenden, werden die Zahlen, genauso wie bei der Integerdivision, zuerst mit CINT() gerundet.

MOD kann bei manchen Aufgaben sehr praktisch sein. Es bietet etwa eine sehr bequeme Möglichkeit, zu überprüfen, ob eine Zahl gerade oder ungerade ist. Schließlich bleibt bei ungeraden Zahlen bei einer Teilung durch 2 der Rest 1 — bei geraden Zahlen nicht. Außerdem können Sie es verwenden, wenn Sie sich in einem zyklischen Datenfeld bewegen wollen, z. B. wenn Sie ein mit Daten gefülltes Array haben, bei dem Sie beim Überschreiten einer Array-Grenze wieder auf der anderen Seite fortfahren wollen. Auch die Minutenangabe einer Zeitberechnung fällt in diesen Bereich: nach der 59. Minute wird wieder bei 0 begonnen.

Quelltext 14.6: Rechungen mit Modulo
DIM AS STRING monat(...) = { "Januar", "Februar", "Maerz", "April", "Mai", "Juni", _
                  "Juli", "August", "September", "Oktober", "November", "Dezember" }
DIM AS INTEGER zahl
INPUT "Geben Sie eine Zahl ein: ", zahl

IF zahl MOD 2 THEN
  PRINT "Die Zahl ist ungerade!"
ELSE
  PRINT "Die Zahl ist gerade!"
END IF

PRINT "Der 6. Monat im Jahr ist der "; monat(5)
PRINT zahl; " Monate spaeter ist "; monat((5+zahl) MOD 12)
SLEEP
Ausgabe:
Geben Sie eine Zahl ein: 17
Die Zahl ist ungerade!
Der 6. Monat im Jahr ist der Juni
 17 Monate spaeter ist November

Zu Zeile 6: zahl MOD 2 wird als Wahrheitswert interpretiert und ist daher genau dann erfüllt, wenn bei der Division ein Rest bleibt. Sie hätten auch direkt das Ergebnis der Modulo-Berechnung überprüfen können:

IF (zahl MOD 2) = 0 THEN PRINT "Die Zahl ist gerade."
' oder
IF (zahl MOD 2) = 1 THEN PRINT "Die Zahl ist ungerade."
' Da Zeile 3 nicht mit negativen Zahlen funktioniert, waere besser:
' IF (zahl MOD 2) <> 0 THEN PRINT "Die Zahl ist ungerade."

Beachten Sie beim Monats-Array, dass die Zählung, bei 0 beginnt. monat(0) ist Januar und monat(11) ist Dezember. Das ist vielleicht eine ungewöhnliche Bezeichnung, aber für die Modulo-Berechnung äußerst praktisch. Bei der Berechung MOD 12 erhalten Sie ja einen Wert von 0 bis 11.

Leider funktioniert zwar das Addieren, beim Subtrahieren stoßen wir aber auf das Problem mit dem möglicherweise negativen Ergebnis.

Quelltext 14.7: Subtraktion mit Modulo
DIM AS STRING monat(11) = { "Januar", "Februar", "Maerz", "April", "Mai", "Juni", _
                 "Juli", "August", "September", "Oktober", "November", "Dezember" }
DIM AS INTEGER aktuell = 5

' funktioniert problemlos:
PRINT "8 Monate nach "; monat(aktuell) " kommt "; monat((aktuell+8) MOD 12)
' funktioniert nicht (Zugriff ausserhalb des Speicherbereichs!):
' PRINT "8 Monate vor "; monat(aktuell) " kommt "; monat((aktuell-8) MOD 12)
' stattdessen funktioniert:
PRINT "8 Monate vor "; monat(aktuell) " kommt "; monat((aktuell+4) MOD 12)

Acht Monate zurück ist dasselbe wie vier Monate nach vorn, zumindest was den Monatsnamen angeht. Etwas aufwändiger wird es, wenn Sie, wie in Quelltext 14.6 bei der Addition, den zu subtrahierenden Wert nicht kennen. In diesem Fall können Sie das Ergebnis der Modulo-Berechnung zunächst zwischenspeichern und bei einem negativen Ergebnis den zugehörigen positiven Wert berechnen. Wenn sichergestellt ist, dass Sie weniger als 12 subtrahieren (bzw. weniger als den Wert rechts von MOD), können Sie die Sache vereinfachen, indem Sie links noch einmal 12 addieren.

DIM AS INTEGER zwischenwert = alterWert - abzug
DO UNTIL zwischenwert >= 0                ' Solange der Zwischenwert zu klein ist,
  zwischenwert += 12                      '     addiere 12 zum Zwischenwert.
LOOP
PRINT monat(zwischenwert MOD 12)               ' klappt fuer jeden 'abzug'
PRINT monat((alterWert - abzug + 12) MOD 12)   ' klappt nur, wenn 'abzug' <= 12

Da sich die Zahlenwerte der Berechnung MOD 12 immer nach zwölf Zahlen wiederholt, können Sie links beliebig oft 12 addieren, ohne dass sich das Ergebnis ändert.

14.1.6 Zufallszahlen

Auch die Computer-Zufallszahlen gehören in den Bereich der mathematischen Funktionen. FreeBASIC kann nämlich (wie andere Programmiersprachen auch) keine echten Zufallszahlen erzeugen, sondern verwendet einen sogenannten Pseudozufallszahlengenerator, der anhand bestimmter Voraussetzungen die nächste „Zufallszahl“ berechnet. Der dabei verwendete Mersenne-Twister-Algorithmus wurde so geschickt ausgetüftelt, dass die erzeugten Zahlen für den Anwender wie Zufallszahlen erscheinen. Unter anderem heißt das, dass die kommende Zahlenfolge für den Benutzer unvorhersehbar ist.

Diese Unvorhersehbarkeit hat natürlich ihre Grenzen. Immerhin handelt es sich immer noch um einen (zudem bekannten) Algorithmus, und was der Algorithmus berechnet, muss sich auch auf anderem Weg berechnen lassen. Wenn ausreichend viele Zahlen der Zufallsfolge vorliegen, lässt sich theoretisch tatsächlich die weitere Folge berechnen. Für sicherheitsrelevante Anwendungen ist der Algorithmus daher nur bedingt geeignet. Für andere Programme, etwa für zufallsbedingte Spiele, ist die Pseudozufälligkeit aber mehr als ausreichend.

Auch wenn Sie die Funktionsweise des Mersenne-Twister gar nicht so genau kennen müssen, ist ein klein wenig Hintergrundwissen dennoch hilfreich. Der Algorithmus benötigt eine Gruppe von Startwerten, aus denen er dann die weiteren Zufallszahlen berechnet. Das bedeutet: Bevor man den Zufallsgenerator sinnvoll nutzen kann, muss er zuerst initialisiert werden. Dazu dient der Befehl RANDOMIZE.

RANDOMIZE [initialwert] [, algorithmus]

Anhand von initialwert (im Englischen seed genannt) werden die Startwerte für den Algorithmus bestimmt. Daher werden Sie immer, wenn Sie denselben Startwert verwenden, auch dieselbe Zufallsfolge erhalten. Das ist natürlich in den meisten Fällen nicht gewünscht. Wenn Sie jedoch startwert einfach weglassen, wählt FreeBASIC selbständig einen Initialwert, der auf der systeminternen Zeitmessung TIMER() basiert (siehe Kapitel 18.1.2). Möglicherweise finden Sie auch Quelltexte, in denen TIMER() als Startwert angegeben wird. Das war früher ein beliebtes Mittel, um einen „zufälligen“ Initialwert zu erhalten. Bei FreeBASIC ist es dagegen empfehlenswert, initialwert wegzulassen, um die bestmögliche Initialisierung zu erhalten.

algorithmus legt den Algorithmus für die Zufallszahl-Berechnung fest. Wie bereits gesagt, wird normalerweise der Mersenne-Twister verwendet. Sie können aber auch z. B. gezielt einen speichereffizienteren Algorithmus wählen (auf Kosten der hohen Zufälligkeit) oder den Algorithmus von QuickBASIC, der zwar weitaus schwächer ist, aber für die Kompatibilität eines älteren Programmes interessant sein könnte. Wenn Sie einfach nur einen guten, soliden Zufallsgenerator verwenden wollen, lassen Sie diesen Parameter weg.

Note Hinweis:
RANDOMIZE wird in der Regel nur ein einziges Mal aufgerufen, nämlich bevor Sie die erste Zufallszahl abfragen. Es ist keinesfalls nötig und auch nicht zu empfehlen, RANDOMIZE vor jeder weiteren Zufallszahl erneut aufzurufen!

Um anschließend auf die Zufallszahlen zuzugreifen, benötigen Sie RND(). Sie erhalten eine DOUBLE-Zahl von 0 bis 1 (genauer: von einschließlich 0 bis ausschließlich 1). Bei jedem Aufruf von RND() erhalten Sie eine neue Zahl vom Generator.

Quelltext 14.8: Zufallszahlen ausgeben
RANDOMIZE
FOR i AS INTEGER = 1 TO 10  ' zehn Zufallszahlen ausgeben
  PRINT RND
NEXT
SLEEP

Wenn Sie das Programm mehrmals starten, werden Sie sehen, dass Sie jedesmal eine neue Folge an Zahlen erhalten. Wenn Sie allerdings die erste Zeile weglassen oder einen festen Initialwert angeben (z. B. RANDOMIZE 18), bekommen Sie bei jedem Start dieselbe Folge.

Note Hinweis:
Die Angabe eines Initialwertes wird notwendig, wenn Sie zufällige Werte generieren wollen, die reproduzierbar sein sollen — etwa wenn das Programm zufallsgesteuert ein Spielfeld erstellen soll, das auf einem anderen Rechner durch Übermittlung des Initialwertes ganz genauso nachgebaut werden soll.

Da man in den seltensten Fällen Zufallswerte im Bereich von 0 bis 1 benötigt, werden Sie die mit RND() ermittelte Zahl in der Regel noch so modifizieren, dass eine Zufallszahl in einem von Ihnen gewünschten Bereich herauskommt. Wir wollen das an der Simulation eines Würfelwurfes demonstrieren. Der gewünschte Zahlenbereich geht von 1 bis 6, außerdem werden nur Ganzzahlen benötigt. Wenn RND() mit 6 multipliziert wird und man anschließend die Nachkommastellen abschneidet, erhält man Ganzzahlen von 0 bis 5. Nun nur noch 1 addiert, und schon ist der Würfelwurf-Simulator fertig.

Quelltext 14.9: Würfelwurf simulieren
RANDOMIZE
PRINT "Ich werde zehnmal fuer Sie wuerfeln. Die Ergebnisse lauten:"
FOR i AS INTEGER = 1 TO 10  ' zehn Zufallszahlen ausgeben
  PRINT INT(RND*6) + 1
NEXT
SLEEP

Entscheidend bei der Simulation eines Würfelwurfes ist, dass jede Zahl mit derselben Wahrscheinlichkeit errechnet wird. Versuchen Sie nachzuvollziehen, warum das bei der hier gewählten Berechnung der Fall ist.

Der Vollständigkeit halber sei noch erwähnt, dass Sie RND() einen Parameter mitgeben können. Ist der Parameter 0, gibt der Befehl ein weiteres Mal die letzte ausgegebene Zufallszahl zurück. Bei allen anderen Parameterwerten erhalten Sie die nächste „neue“ Zufallszahl.

Tip Unterschiede zu QuickBASIC:
In QuickBASIC hat der optionale Parameter eine andere Bedeutung. Die Dialektform -lang qb arbeitet hier genauso wie QuickBASIC.

14.2 Zahlensysteme

Neben dem uns geläufigen Dezimalsystem werden im Computerbereich noch drei weitere Zahlensysteme verwendet: das Binär-, das Oktal- und das Hexadezimalsystem. Allen gemeinsam ist, dass es sich um Stellenwertsysteme handelt, d. h. die Position einer Ziffer innerhalb der Zahl gibt ihre Wertigkeit an. Während im Dezimalsystem die zweite Ziffer von rechts die Wertigkeit 10 besitzt, die dritte Ziffer von rechts die Wertigkeit 10·10 = 100 usw., ist die Basis des Binärsystem die 2, die Basis des Oktalsystems ist 8 und die Basis des Hexadezimalsystems 16. Die Oktalzahl 1238 hätte also den dezimalen Wert 1·64 + 2·8 + 3 = 83 (die tiefgestellte 8 in 1238 zeigt an, dass es sich um eine Oktalzahl handelt). Grundsätzlich wären auch Zahlensysteme zu ganz anderen Basen möglich.3

Für das Binärsystem stehen nur die zwei Ziffern 0 und 1 zur Verfügung, für das Oktalsystem die acht Ziffern von 0 bis 7. Für das Hexadezimalsystem werden 16 Ziffern benötigt. Da wir im Dezimalsystem nur zehn Ziffern zur Verfügung haben, behilft man sich für die fehlenden sechs Ziffern mit den Buchstaben A bis F. D16 ist damit der Dezimalwert 13 und 1A16 der Dezimalwert 26 (1·16 + 10).

14.2.1 Darstellung in Nicht-Dezimalsystemen

Im Quelltext auftretende Zahlen interpretiert FreeBASIC zunächst als Dezimalzahlen. So, wie oben die tiefgestellten Werte verwendet wurden, um das verwendete Zahlensystem zu kennzeichnen (wie D16 für das Hexadezimalsystem), muss es auch im Programm explizit kenntlich gemacht werden, wenn Sie nicht das Dezimalsystem meinen. FreeBASIC verwendet dazu die et-Ligatur &, die, zusammen mit einem Kennbuchstaben für das gewünschte System, vor die Zahl geschrieben wird:

  • &b für Binärzahlen (z. B. &b1101=13)

  • &o für Oktalzahlen (z. B. &o37=31)

  • &h für Hexadezimalzahlen (z. B. &h1F=31)

Sowohl für den Kennbuchstaben als auch die Ziffern A-F im Hexadezimalsystem ist Groß- und Kleinschreibung nicht von Bedeutung.

Mit dieser neuen Schreibweise können Zahlen anderer Systeme genauso im Programm verwendet werden, wie Sie es von Dezimalzahlen gewohnt sind. Nur Gleitkommazahlen anderer Systeme sind in FreeBASIC nicht möglich.

PRINT (420 + &hfe) / &b10
' identisch mit PRINT (420 + 254) / 2
SLEEP

14.2.2 BIN(), OCT() und HEX()

FreeBASIC selbst zeigt Zahlen ausschließlich im Dezimalsystem an. Wenn Sie eine Anzeige in einem anderen Zahlensystem wünschen, kann diese nur über einen String erfolgen. Die Umwandlung läuft über die Funktionen BIN() (Binärsystem), OCT() (Oktalsystem) und HEX() (Hexadezimalsystem). Der Rückgabewert ist ein String, der die Ziffernfolge im jeweiligen System enthält. BIN() wurde ja schon in Kapitel 10.2 eingesetzt.

Wenn Sie übrigens den Zahlenwert als WSTRING haben wollen, verwenden Sie stattdessen die Funktionen WBIN(), WOCT() und WHEX(). Wir werden uns allerdings erst einmal weiter mit normalen Strings beschäftigen.

Quelltext 14.10: Vom Dezimalsystem in ein anderes System umrechnen
DIM AS INTEGER zahl
INPUT "Geben Sie eine Zahl ein: ", zahl
PRINT "in Binaerschreibweise: "; BIN(zahl)
PRINT "im Oktalsystem         "; OCT(zahl)
PRINT "im Hexadezimalsystem:  "; HEX(zahl)
SLEEP
Ausgabe:
Geben Sie eine Zahl ein: 42
in Binaerschreibweise: 101010
im Oktalsystem         52
im Hexadezimalsystem:  2A

Wenn Sie nachrechnen wollen: Für das Binärsystem ergibt sich 32+8+2 = 42, im Oktalsystem 5·8+2 = 42 und im Hexadezimalsystem 2·16+10 = 42. Interessant ist sicher auch der Umgang mit negativen Zahlen:

Ausgabe:
Geben Sie eine Zahl ein: -42
in Binaerschreibweise: 11111111111111111111111111010110
im Oktalsystem         37777777726
im Hexadezimalsystem:  FFFFFFD6

Beim Wert -1 sind alle Bits gesetzt, das entspricht (in einem 32-Bit-System) der maximalen Oktalzahl 37777777777 bzw. der maximalen Hexadezimalzahl FFFFFFFF. Für jeden Schritt weiter ins Negative wird dieser Maximalwert um 1 reduziert.

BIN(), OCT() und HEX() erlauben einen zusätzlichen zweiten Parameter, der die Länge des zurückgegebenen Strings angibt. Das kann genutzt werden, um ihn vorn bei Bedarf mit Nullen aufzufüllen und dadurch eine einheitliche Länge zu erhalten, aber auch, um führende Ziffern abzuschneiden, die für Sie nicht interessant sind.

PRINT "Binaerer Vergleich von 10 und -10"
DIM AS UBYTE x = 10
PRINT BIN( x, 8)
PRINT BIN(-x, 8)

BIN(-x) würde, je nach System, 32 oder 64 Stellen ausgeben. Da Sie aber mit einem UBYTE arbeiten, sind für Sie vermutlich nur die hintersten acht Ziffern von Belang.

Da die mit BIN() usw. zurückgegebenen Werte Zeichenketten sind, können Sie mit ihnen natürlich nicht einfach so weiterrechnen. Das ist allerdings nicht schlimm, da sich die Berechnungen in anderen Zahlensystemen nicht voneinander unterscheiden; es handelt sich lediglich um eine andere Darstellungsform der Werte bzw. der Ergebnisse. Sie können also getrost die Rechnung im Dezimalsystem durchführen (tatsächlich nutzt der Prozessor intern ja das Binärsystem) und das Ergebnis in das von Ihnen gewünschte System umwandeln.

Um mit Zahlen, die nach der Verwendung von BIN() usw. in einem String vorliegen, weiterzurechnen, können Sie sie mit CINT() (oder einer anderen Funktion aus dieser Gruppe) in das Dezimalsystem umwandeln. Beachten Sie aber, dass die Information, um welches System es sich aktuell handelt, im String nicht gespeichert wurde. Sie müssen sie also wieder „nachtragen“, und zwar wie oben durch ein & und den Kennbuchstaben, die vor dem Zahlenwert eingefügt werden müssen.

Quelltext 14.11: Umwandlung ins Binärsystem und zurück
DIM AS INTEGER dezimal = 19
DIM AS STRING  binaer  = BIN(dezimal)
PRINT "Die Dezimalzahl " & dezimal & " lautet binaer " & binaer
PRINT "Die Binaerzahl " & binaer & " lautet dezimal " & CINT("&b" & binaer)
SLEEP

Dem Binärzahl-String wird also vorn ein "&b" angefügt und das Ganze dann in die Funktion CINT() eingefügt.

Ausgabe:
Die Dezimalzahl 19 lautet binaer 10011
Die Binaerzahl 10011 lautet dezimal 19

Für Oktalzahlen erlaubt FreeBASIC hier — und nur hier — auch das Weglassen des Kennbuchstaben o. Die et-Ligatur allein würde reichen. Mit dem o wird es jedoch deutlicher, welches Zahlensystem genau gemeint ist.

14.3 Bit-Manipulationen

Wie Sie ja bereits wissen, werden alle Daten intern als eine Ansammlung von Bits verwaltet. Umgekehrt könnte man auch sagen: Eine Gruppe von Bitwerten lässt sich auch als anderes Datenformat interpretieren, z. B. als Ganzzahl. Tatsächlich werden gern Ganzzahl-Datentypen verwendet, wenn mit Bits gearbeitet werden soll. So können die Werte sehr kompakt zwischen verschiedenen Programmteilen oder auch verschiedenen Programmen ausgetauscht werden.

Falls Sie sich fragen, was man mit einzelnen Bits denn so Interessantes anstellen kann: Jede Information, die auf zwei Zuständen beruht (wahr oder falsch, an oder aus, ja oder nein) entspricht einer Bit-Information. Wenn Sie etwa eine Eingabemaske bereitstellen, in der der Benutzer wählen kann, welche Optionen er nutzen will und welche nicht, stellt die Wahl für jede einzelne Option (angewählt oder abgewählt) ein Bit an Information dar. Die Bit-Manipulation von Ganzzahlen ist dann eine einfache Möglichkeit zur Verwaltung, wenn Bitfelder für Ihre Zwecke zu aufwendig sind (vgl. dazu Kapitel 7.4).

Im Übrigen werden natürlich auch Gleitkommazahlen binär gespeichert; hier macht der Zugriff auf einzelne Bit jedoch in aller Regel keinen Sinn, da sich das verwendete Speicherformat4 nicht sehr für Direktmanipulationen eignet.

14.3.1 Manipulation über Bitoperatoren

In Kapitel 10.2.4 wurde bereits das Verhalten der Operatoren AND, OR, XOR usw. besprochen. Diese können gezielt dazu verwendet werden, den Status bestimmter Bits abzufragen. Ein einfacher Fall ist die bereits in Kapitel 14.1.5 angedachte Überprüfung, ob eine Zahl gerade ist. Da bei ungeraden Zahlen das niedrigste Bit gesetzt ist und bei geraden Zahlen nicht, kann man die Geradzahligkeit auch über AND prüfen.

Quelltext 14.12: Geradzahligkeit mit AND prüfen
DIM AS INTEGER x
INPUT "Geben Sie eine ganze Zahl ein: ", x
PRINT "Binaere Schreibweise der Zahl: "; BIN(x)
IF x AND 1 THEN
  PRINT "Das niedrigste Bit ist gesetzt, also ist"; x; " ungerade."
ELSE
  PRINT "Das niedrigste Bit ist nicht gesetzt, also ist"; x; " gerade."
END IF
SLEEP
Ausgabe:
Geben Sie eine ganze Zahl ein: 42
Binaere Schreibweise der Zahl: 101010
Das niedrigste Bit ist nicht gesetzt, also ist 42 gerade.

Hilfreich sind die Bitoperatoren, wenn die einzelnen Bit einer Zahl jeweils eigene Informationen ausdrücken sollen. Nehmen wir als Beispiel eine Variable, die verschiedene Anzeigeoptionen eines Textes beinhalten soll. Wir legen (willkürlich) fest, dass Bit 05 angibt, ob der Text fett gedruckt sein soll. Bit 1 gibt eine Unterstreichung des Textes an, Bit 2 eine Kursivschrift. In Dezimalwerten ausgedrückt heißt das dann 1=fett, 2=unterstrichen, 4=kursiv. Kombinationen lassen sich durch eine Verknüpfung mehrerer Werte erreichen, z. B. 3=fett+unterstrichen, 6=unterstrichen+kursiv und 7=alle Attribute.

Ich habe hier bewusst allgemein von Verknüpfung gesprochen und nicht von Addition. Denn auch wenn der Wert für fett+unterstrichen durch die Addition von fett und unterstrichen entsteht, würde eine Addition von fett und fett den Wert unterstrichen ergeben — was natürlich keinen Sinn ergibt. Stattdessen kann die OR-Verknüpfung eingesetzt werden.

DIM AS INTEGER formatierung = 1 OR 4  ' fett und kursiv
PRINT "Formatierungsmuster: "; BIN(formatierung, 3)

IF (formatierung AND 1) = 1 THEN PRINT "fett"
IF (formatierung AND 2) = 2 THEN PRINT "unterstrichen"
IF (formatierung AND 4) = 4 THEN PRINT "kursiv"
SLEEP

Hier weicht die Verknüpfungslogik vom allgemeinen Sprachgefühl des Alltags ab. Wenn die Formatierung auf fett UND kursiv gesetzt werden soll, werden die zugehörigen Zahlen mit OR verknüpft. Das mag auf den ersten Blick verwirren. Sie sollten sich jedoch klar machen, dass AND und OR auf Bit-Ebene arbeiten und dass OR im Ergebnis immer dann ein Bit setzt, wenn bei mindestens einem der Operanden das Bit gesetzt ist. 1 OR 4 setzt daher Bit 0 (wegen der 1) und Bit 2 (wegen der 4).

AND dagegen prüft, welche Bit bei beiden Operanden gleichzeitig gesetzt sind. Mit formatierung AND 2 können Sie feststellen, ob das Bit für die Unterstreichung gesetzt ist oder nicht, unabhängig davon, wie die anderen beiden Formatierungsarten eingestellt sind. Sie können auch prüfen, ob z. B. fett und kursiv gleichzeitig aktiviert sind oder ob beide deaktiviert sind — oder vieles mehr.

Bevor wir dazu ein Beispiel ansehen, gibt es noch einen Verbesserungsvorschlag zur Lesbarkeit. Da das Programm sehr kurz ist, kann man sich die Zuordnung 1=fett usw. noch recht gut merken. Bei größeren Projekten und längerer Entwicklungsdauer kann das aber schon schwierig werden, gerade weil Sie mit der Zeit sicher auch noch andere Werte aus anderen Bereichen ins Programm einführen, deren Bedeutungen man sich alle merken müsste. Stattdessen ist es einfacher, die Werte in Konstanten mit sprechenden Namen abzulegen.

Quelltext 14.13: Formateigenschaften mit OR und AND setzen und lesen
CONST FETT = 1, UNTERSTRICH = 2, KURSIV = 4
DIM AS INTEGER formatierung = FETT OR KURSIV
PRINT "Formatierungsmuster: "; BIN(formatierung, 3)

' Ist Fettschrift aktiviert?
IF (formatierung AND FETT) = FETT THEN PRINT "fett"

' Ist sowohl Fett- als auch Kursivschrift aktiviert?
IF (formatierung AND (FETT OR KURSIV)) = (FETT OR KURSIV) THEN
  PRINT "Fett und kursiv? Uebertreiben Sie es bitte nicht!"
END IF

' Sind alle drei Formatierungsformen deaktiviert?
IF (formatierung AND (FETT OR UNTERSTRICH OR KURSIV)) = 0 THEN
  PRINT "Es wurde keine Formatierung gewaehlt."
END IF

' nachtraeglich Fettschrift aktivieren
formatierung OR= FETT
PRINT "Fettschrift wurde aktiviert - neues Formatierungsmuster: ";
PRINT BIN(formatierung, 3)
SLEEP
Ausgabe:
Formatierungsmuster: 101
fett
Fett und kursiv? Uebertreiben Sie es bitte nicht!
Fettschrift wurde aktiviert - neues Formatierungsmuster: 101

Da Fettschrift schon von Beginn an aktiviert ist, darf sich durch eine „nachträgliche“ Aktivierung nichts ändern. Mit OR ist das kein Problem, während eine Addition von FETT zu einem völlig falschen Ergebnis geführt hätte. Sie sehen in Quelltext 14.13 übrigens, dass auch für die Bitoperatoren die Kurzschreibweise OR= usw. zulässig ist.

Sie sehen, dass man mit logischen Operatoren eine ganze Menge anstellen kann, allerdings dauert es vielleicht anfangs eine Weile, bis man sich an die Arbeitsweise gewöhnt hat. Um effektiv programmieren zu können, ist allerdings das Verständnis der Bitmanipulationen unerlässlich.

Note Hinweis:
Wenn Sie mit der Funktionsweise der Bitoperatoren herumspielen, lassen Sie sich die Ergebnisse mit BIN() ausgeben. Dadurch sehen Sie am besten, welche Wirkung die einzelnen Operatoren erzielen.

Um — unabhängig von der aktuellen Einstellung — eine der Formatierungsbefehle umzustellen, bietet sich XOR an. Zur Erinnerung: XOR setzt im Ergebnis genau dann ein Bit, wenn sich die beiden entsprechenden Bit der beiden Operatoren unterscheiden, also genau dann, wenn es in einem der beiden Operatoren gesetzt ist und im anderen nicht.

CONST FETT = 1, UNTERSTRICH = 2, KURSIV = 4
DIM AS INTEGER formatierung = FETT OR KURSIV
PRINT "Formatierungsmuster vorher: "; BIN(formatierung, 3)

' Kursivschrift umstellen
formatierung XOR= KURSIV
PRINT "Formatierungsmuster danach: "; BIN(formatierung, 3)
Ausgabe:
Formatierungsmuster vorher: 101
Formatierungsmuster danach: 001

Auch wenn Bitfelder meist einfacher in der Handhabung sind, gibt es doch Gründe für den Einsatz der Bitoperatoren. Um Bitfelder zu verwenden, ist die Deklaration eines UDTs nötig, und das gleichzeitige Setzen mehrerer Formatangaben wäre nicht möglich. Wenn Sie etwa ein Unterprogramm einsetzen wollen, dem die gewünschte Formatierung übergeben werden soll, ist es deutlich leichter, diese schnell über einen Zahlenwert zusammenzusetzen, als zuerst das passende UDT zu bauen (auch dazu gibt es aber programmiertechnische Lösungen). Ein schlagendes Argument ist aber die bessere Kompatibilität zu anderen Programmiersprachen, da Ganzzahldatentypen in nahezu jeder Sprache zur Verfügung stehen.

14.3.2 Einzelne Bit lesen und setzen

Wenn Sie gezielt ein einzelnes Bit auslesen wollen, kommt auch der Befehl BIT() infrage.

PRINT "Bit-Status des dritten Bit der Zahl 123:";
PRINT BIT(123, 3)
SLEEP

Die Nummerierung der Bits beginnt wie üblich bei 0 für das niederste Bit. Die Funktion gibt -1 (=wahr) zurück, wenn das Bit gesetzt ist, und 0, wenn das nicht der Fall ist. Um ein bestimmtes Bit zu setzen, dient BITSET(), um es zu löschen (also auf 0 zu setzen) BITRESET().

DIM AS INTEGER zahl = 123
PRINT zahl; " mit gesetztem 2. Bit:  "; BITSET(zahl, 2)
PRINT zahl; " mit geloeschtem 3. Bit:"; BITRESET(zahl, 3)
SLEEP

Beachten Sie, dass BITSET() und BITRESET() nicht den Variablenwert selbst verändern, sondern lediglich den Wert zurückgeben, der durch die Veränderung entsteht. Wollen Sie in der Variablen selbst ein Bit verändern, müssen Sie das Ergebnis von BITSET() bzw. BITRESET() wieder der Variablen zuweisen.

Die in Quelltext 14.13 eingesetzte Einstellung von Formatangaben lässt sich mit den drei Befehlen ebenfalls umsetzen. Da nun die Position des Bits gefragt ist und nicht seine Wertigkeit, geht die Nummerierung nun von 0 bis 2 statt über die Werte 1, 2 und 4. Deswegen lässt sich hier sehr gut ENUM einsetzen. Ein Vorteil ist zudem, dass möglicherweise später neu hinzukommende Formateigenschaften nur zur Liste hinzugefügt werden müssen, ohne eine Berechnung der Wertbelegung vornehmen zu müssen.

Quelltext 14.14: Formateigenschaften mit BITSET() und BIT() setzen und lesen
ENUM FORMATE
  fett, unterstrich, kursiv
END ENUM
DIM AS INTEGER formatierung
formatierung = BITSET(formatierung, FORMATE.fett)   ' Fettschrift aktivieren
formatierung = BITSET(formatierung, FORMATE.kursiv) ' Kursivschrift aktivieren
PRINT "Formatierungsmuster: "; BIN(formatierung, 3)

' Ist Fettschrift aktiviert?
IF BIT(formatierung, FORMATE.fett) THEN PRINT "fett"

' Ist sowohl Fett- als auch Kursivschrift aktiviert?
IF BIT(formatierung, FORMATE.fett) AND BIT(formatierung, FORMATE.kursiv) THEN
  PRINT "Fett und kursiv? Uebertreiben Sie es bitte nicht!"
END IF

' Sind alle drei Formatierungsformen deaktiviert?
IF BIT(formatierung, FORMATE.fett) = 0 _
   AND BIT(formatierung, FORMATE.unterstrich) = 0 _
   AND BIT(formatierung, FORMATE.kursiv) = 0 THEN
   PRINT "Es wurde keine Formatierung gewaehlt."
END IF

' nachtraeglich Fettschrift aktivieren
formatierung = BITSET(formatierung, FORMATE.fett)
PRINT "Fettschrift wurde aktiviert - neues Formatierungsmuster: ";
PRINT BIN(formatierung, 3)
SLEEP

Die Ausgabe ist identisch mit der Ausgabe zu Quelltext 14.13.

14.3.3 Vergleich

Beide Varianten — Bitoperatoren und BITSET()/BITRESET() — haben ihre Vorteile. Bitoperatoren sind nicht nur auf 1-Bit-Werte beschränkt, und es ist auch ein gleichzeitiger Zugriff auf mehrere Formatangaben möglich. Auf der anderen Seite ist das Setzen eines einzelnen Bits mit BITSET() deutlich anschaulicher. Auch das Löschen eines Bits mit Bitoperatoren sieht recht umständlich aus, während es mit BITRESET() sehr klar ist. Wenn Sie allerdings die Bitrechnung beherrschen, stellt auch das kein großes Problem dar.

formatierung = BITRESET(formatierung, FORMATE.fett)   ' keine Fettschrift mehr
formatierung AND= NOT FETT                            ' Alternative mit AND

14.3.4 LOBYTE() und seine Freunde

Ähnlich wie BIT(), aber auf einen größeren Datenbereich bezogen, wirkt LOBYTE(). Wie der Name andeutet, wird hier nicht ein Bit-, sondern ein Byte-Zustand abgefragt, und zwar das untere Byte der übergebenen Zahl (LOw: niedrig). LOBYTE(zahl) ist identisch mit zahl AND &hFF, d. h. alles außer den unteren acht Bit (oder eben des unteren Byte) der Zahl wird ignoriert. Die eng verwandte Funktion HIBYTE() (HIgh: hoch) gibt das Byte oberhalb des LOBYTE() zurück.

Wenn mit Byte-Zuständen gearbeitet wird, verwendet man gern das Hexadezimalsystem, da dort ein Byte genau aus zwei Ziffern besteht. Hexadezimal gesprochen sind LOBYTE() die untersten zwei Ziffern und HIBYTE() die beiden Ziffern links davon.

Noch größere Bereiche decken LOWORD() und HIWORD() ab. Ein Word bzw. Datenwort ist eine Dateneinheit von zwei Byte. Wie sich jetzt leicht erschließen lässt, enthält ein LOBYTE() die unteren beiden Byte des übergebenen Parameters und HIBYTE() die zwei Byte darüber.

Falls sich das noch etwas kompliziert anhört — ein Beispiel sollte hier Klarheit schaffen.

Quelltext 14.15: Byte- und Word-Zugriff
DIM AS LONGINT zahl(2) = { &h123, &hC0DEBA5E, &hFEEDBABEF00D }
PRINT "Vorgabewert  | HIBYTE | LOBYTE | HIWORD | LOWORD"
PRINT "=============+========+========+========+======="
FOR i AS INTEGER = 0 TO 2
  PRINT HEX(zahl(i), 12); " |   ";
  PRINT HEX(HIBYTE(zahl(i)), 2); "   |   ";
  PRINT HEX(LOBYTE(zahl(i)), 2); "   |  ";
  PRINT HEX(HIWORD(zahl(i)), 4); "  |  ";
  PRINT HEX(LOWORD(zahl(i)), 4)
NEXT
SLEEP
Ausgabe:
Vorgabewert  | HIBYTE | LOBYTE | HIWORD | LOWORD
=============+========+========+========+=======
000000000123 |   01   |   23   |  0000  |  0123
0000C0DEBA5E |   BA   |   5E   |  C0DE  |  BA5E
FEEDBABEF00D |   F0   |   0D   |  BABE  |  F00D

14.3.5 Bit-Verschiebung

Es gibt noch zwei Operatoren, die eigentlich in den Bereich der mathematischen Operatoren fallen, aber vorwiegend zur Bit-Manipulation eingesetzt werden: SHL (SHift Left) schiebt in einer Ganzzahl alle Bit um eine angegebene Anzahl an Stellen nach links, SHR (SHift Right) schiebt sie nach rechts.

An einem konkreten Beispiel bedeutet das: Die Zahl 27 hat die Binärdarstellung 110112. Schiebt man alle Bit um eins nach links, erhält man 1101102 bzw. die Dezimalzahl 54. Schiebt man dagegen alle Bit um eins nach rechts, erhält man 11012 bzw. die Dezimalzahl 13. Ein Bit, das „über den Rand hinaus“ geschoben wird, verschwindet also — und zwar auch bei SHL, wenn der Datentyp, in dem das Ergebnis gespeichert werden soll, zu klein ist.

Die Syntax für SHL (und selbstverständlich analog dazu auch für SHR) sieht folgendermaßen aus:

' Zuweisung in eine neue Variable
Ergebnis = Ausgangswert SHL Anzahl
' Kurzschreibweise
Wert SHL= Anzahl   ' ist identisch mit 'Wert = Wert SHL Anzahl'

Ausgangswert ist der Wert, für den die Verschiebung stattfinden soll, und Anzahl ist die Zahl der Stellen, um die verschoben werden soll. In der Kurzschreibweise +SHL=+ wird der Wert des Ergebnisses direkt wieder in der Ausgangszahl gespeichert; ansonsten verändern SHL und SHR den Wert ihrer Operanden nicht. Natürlich können die Operatoren auch in Berechnungen eingebaut werden, ebenso wie z. B. der Operator + oder OR.

Tatsächlich erreichen Sie mit SHL dasselbe wie bei der Multiplikation mit einer Zweierpotenz und mit SHR entsprechend dasselbe wie bei der Ganzzahl-Division. Allerdings arbeiten SHL und SHR in der Regel schneller. Bei der Verwendung vorzeichenbehafteter Typen und einer Verschiebung über die Datentypgrenzen ist das Ergebnis möglicherweise überraschend, aber auch hier unterscheidet es sich nicht vom Ergebnis einer passenden Multiplikation.

Quelltext 14.16: SHL und SHR
DIM b AS BYTE, u AS UBYTE
PRINT "12 SHL 3 = "; 12 SHL 3,     "12*(2^3) = "; 12*(2^3)
PRINT "12 SHL 4 = "; 12 SHL 4,     "12*(2^4) = "; 12*(2^4)
PRINT "12 SHL 5 = "; 12 SHL 5,     "12*(2^5) = "; 12*(2^5)
PRINT

b = 12 SHL 4
PRINT "12 SHL 4 (als BYTE)  = " & b
u = 12 SHL 4
PRINT "12 SHL 4 (als UBYTE) = " & u
b = 12 SHL 5
PRINT "12 SHL 5 (als BYTE)  = " & b
u = 12 SHL 5
PRINT "12 SHL 5 (als UBYTE) = " & u
PRINT

PRINT "12 SHR 2 = "; 12 SHR 2,,    "12\(2^2) = "; 12^2)
PRINT "12 SHR 3 = "; 12 SHR 3,,    "12\(2^3) = "; 12^3)
Ausgabe:
12 SHL 3 =  96              12*(2^3) =  96
12 SHL 4 =  192             12*(2^4) =  192
12 SHL 5 =  384             12*(2^5) =  384

12 SHL 4 (als BYTE)  = -64
12 SHL 4 (als UBYTE) = 192
12 SHL 5 (als BYTE)  = -128
12 SHL 5 (als UBYTE) = 128

12 SHR 2 =  3               12\(2^2) =  3
12 SHR 3 =  1               12\(2^3) =  1

Da in Quelltext 14.16 alle zur Berechnung verwendeten Werte Konstanten sind, kann der Compiler schon während der Compilierzeit feststellen, dass in Zeile 11 und Zeile 13 über den verfügbaren Bereich hinausgeschoben wird, also dass das Ergebnis zu groß ist, um in ein BYTE bzw. UBYTE zu passen. In diesem Fall gibt der Compiler eine Warnung aus. Der Code wird trotzdem compiliert und kann anschließend ausgeführt werden, trotzdem sollten Sie auf Warnungen des Compilers achten (und sie am besten beheben).

14.4 Fragen zum Kapitel

  1. Schreiben Sie eine Funktion zur Erzeugung von Zufallszahlen: Die Funktion erhält als Parameter einen Start-- und einen Endwert und gibt eine Zufallszahl im Bereich dieser beiden Werte zurück.

  2. Schreiben Sie ein Programm, das in einer vorgegebenen Zahl die unten angegebenen Modifikationen durchführt. Versuchen Sie dabei möglichst mehrere verschiedene Ansätze durchzuführen.
    a) Setze die niedrigsten 4 Bit alle auf 0 (alle anderen Bit bleiben gleich).
    b) Drehe alle Bitwerte um  — aus 1 wird 0 und umgekehrt.
    c) Drehe die Bitwerte der niedrigsten 4 Bit um (alle anderen Bit bleiben gleich).

  3. Überlegen Sie sich eine Umsetung der in Quelltext 14.13 und Quelltext 14.14 verwendeten Formateigenschaften mithilfe der in Kapitel 7.4 vorgestellten Bitfeldern.


Fußnoten:
1) Einen Standard-Datentyp der komplexen Zahlen gibt es in FreeBASIC nicht.

2) Es sei aber daran erinnert, dass ein Buchhaltungsprogramm, das sich auf Gleitkommazahlen verlässt, noch mit ganz anderen Problemen zu kämpfen haben wird!

3) Weitere im Alltag auftretende Zahlensysteme sind das Sechziger-System der Zeitmessung (1 Stunde = 60 Minuten = 3600 Sekunden) und das noch gelegentlich verwendete Zwölfer-System (1 Gros = 12 Dutzend = 144).

4) FreeBASIC verwendet die allgemein übliche Norm IEEE 754.

5) Zur Erinnerung: Bit 0 ist das niedrigste Bit, das in der Binärschreibweise der Zahl ganz rechts steht.


Kapitel 13: Datentypen umwandeln

Inhaltsverzeichnis

Kapitel 15: Stringmanipulation