Nun, allzuoft haben wir es in AutoLisp mit Bits nicht zu tun.
Da aber AutoCAD einige Systemvariablen und auch Elementdaten
binär speichert, muss man sich als AutoLisp-Programmierer auch
damit auskennen. Trotzdem, gegenüber den meisten anderen
Programmiersprachen bleibt uns das Schlimmste erspart:
Hexadezimale Zahlen treten eigentlich nie auf, und das bedeutet,
dass wir uns auf grundlegende Techniken der binären Verarbeitung
beschränken können.
Wir wissen, dass ein Rechner intern ja alles in binärer Form (d.h.
ausschliesslich aus Nullen und Einsen zusammengesetzt) darstellt.
Mit einem einzigen Bit können zwei Zustände kodiert werden, aber
mit zwei Bits sind es schon vier: 00, 01, 10, 11. Drei Bits erweitern
den Bereich schon auf 8 Zustände (000, 001, 010, .... 110, 111).
Es ist nichts schwer nachzuvollziehen, dass es bei 4 Bits dann
16 werden usw.
Es ist doch einfach so, dass sich jedesmal, wenn ein weiteres
Bit hinzukommt, die Anzahl der bisherigen Kombinationen verdoppelt.
Um das nun ein wenig mathematischer zu formulieren: die Anzahl
der Möglichkeiten ist 2 hoch Anzahl der Bits. Ein Byte
(8 Bits) kann also 256 verschiedene Zustände annehmen, denn
2 hoch 8 ist 255.
Eine Bit-Codierung begegnet uns z.B. bei der Systemvariablen
OSMODE - hier werden die derzeit eingestellten (aktiven) Objektfänge
gespeichert. Der Objektfang END hat den Wert 1, und MIT hat den
Wert 2. ZEN hat dann aber schon den Wert 4, und das ist der
springende Punkt: Hätte ZEN den Wert 3, wäre das nicht eindeutig!
3 könnte ja auch bedeuten, dass END und MIT (1 und 2) gleichzeitig
aktiv sind. Daher muss jeder zusätzliche Wert doppelt so gross wie
der Vorgänger ausfallen.
Binär dargestellt, werden die Dinge plötzlich ganz einfach:
00000001 = 1 (END)
00000010 = 2 (MIT)
00000011 = 3 (END + MIT)
00000100 = 4 (ZEN)
00000101 = 5 (ZEN + END)
00000110 = 6 (ZEN + MIT)
00000111 = 7 (ZEN + MIT + END)
00001000 = 8 (PUN)
Wir sehen, dass das erste (rechte) Bit immer für END zuständig ist,
das zweite (von rechts) für MIT usw.
Etwas schwieriger wird folgende Frage: Welche Ofänge sind denn aktiv,
wenn OSMODE gerade den Wert 37 hat? Wer aufmerksam mitgedacht hat,
wird auf Anhieb sehen, dass END auf jeden Fall dabei sein muss, denn
37 ist eine ungerade Zahl! Aber alles Weitere ist auf Anhieb mit
blossem Auge nicht so leicht zu erkennen. AutoLisp stellt uns aber
einige Funktionen für den Umgang mit Bits und Bytes zur Verfügung,
die jetzt hier besprochen werden sollen.
Die erste dieser Funktionen heisst
(logand) - hier liegt allerdings
eine Namensverwirrung vor: Es handelt sich NICHT um das logische
UND (diese Funktion heisst schlicht
(and ...)! Hier geht es um Bits!
Wir müssen also mit dem irreführenden Namen leben. Auf jeden Fall
vergleicht diese Funktionen zwei Zahlen miteinander und gibt als
Ergebnis eine neue Zahl zurück. Dabei passiert Folgendes: Da, wo
in beiden Input-Zahlen ein Bit gesetzt ist, wird es auch im Ergebnis
gesetzt. Ein paar Beispiele:
(logand 19 49) => 17
00010011 Die erste Vergleichszahl ist 19
00110001 Die andere lautet 49
--------
00010001 Das Ergebnis ist 17
(logand 31 63) => 31
00111111 63
00011111 31
--------
00011111 31
(logand 170 85)
10101010 170
01010101 85
--------
00000000 0
Es ist offensichtlich, dass die Reihenfolge der Argumente keine
Rolle spielt. Wenn wir testen wollen, ob z.B. der Ofang SCH (32)
aktiv ist, verwenden wir folgende Formulierung:
(= 32(logand 32 (getvar"OSMODE")))
; prüft, ob Bit 6 gesetzt ist
Die Darstellung wurde bisher auf ein Byte begrenzt. Allerdings
arbeiten die Bit-Funktionen in AutoLisp auch mit grösseren Zahlen,
das Maximum für Ganzzahlen liegt bei AutoCAD/AutoLisp bei 32 Bit.
Schwierig wird die Angelegenheit übrigens bei negativen Zahlen,
da hier die Darstellungsform, d.h. die Bit-Anordnung, nicht ganz
einfach zu verstehen ist. Da der bitweise Umgang mit negativen
Zahlen aber in AutoLisp keine Rolle spielt, werde ich darauf
auch nicht eingehen.
Die Funktion
(logior) ist ähnlich, aber hier werden die Zahlen
nicht verUNDet, sondern verODERt, um's mal auf Programmiererdeutsch
zu sagen. Das bedeutet: Da, wo in einer der beiden Eingangszahlen
(oder auch in beiden) ein Bit vorhanden ist, wird es auch in der
Ausgangszahl gesetzt. Noch einmal unsere Beispielzahlen:
(logior 19 49) => 51
; Erläuterung:
00010011 Die erste Vergleichszahl ist 19
00110001 Die andere lautet 49
--------
00110011 Das Ergebnis ist 51
(logior 31 63) => 63
00111111 63
00011111 31
--------
00111111 63
(logior 170 85) => 255
10101010 170
01010101 85
--------
11111111 255
Ein weiteres Beispiel, mit dem auch gleich einmal der Umgang
mit grösseren Zahlen demonstriert werden kann: Die Werte für
OSMODE gehen von 1 (END) bis 8192 (PARallel). Es gibt dann aber
auch noch den Wert 16384, mit dem der Objektfang temporär
ein- bzw. ausgeschaltet werden kann. Bei diesem Wert handelt
es sich übrigens um das 15. Bit (wer's nich glaubt, sollte
einmal
(expt 2 14) eingeben (nicht 15, sondern 14!).
Um zu testen, ob der Objektfang derzeit deaktiviert ist,
verwenden wir also das hier:
(/= 0 (logior 16384 (getvar "OSMODE")))
;oder
(/= 0 (logior (expt 2 14) (getvar "OSMODE")))
Und so würde der temporär deaktivierte Ofang END + ZEN + SCH
binär aussehen (die Leerstelle in der Binärdarstellung dient
nur der Lesbarkeit):
01000000 00100011 16384 + 37 = 16421
Um die beiden Funktionen
(logand) und
(logior) noch einmal
zu vergleichen, möchte ich hier kurz noch erklären, was eine
'Wahrheitstafel' ist: Nichts weiter als eine kleine Tabelle,
die die Verknüpfung darstellt. Im nächsten Kapitel werden wir
diese Wahrheitstafeln des öfteren brauchen:
In Out In Out
logand: 0 0 | 0 logior: 0 0 | 0
0 1 | 0 0 1 | 1
1 0 | 0 1 0 | 1
1 1 | 1 1 1 | 1
Und nun noch eine paar Worte zum Verschieben von Bits, wozu
uns AutoLisp die Funktion
(lsh ...) bereitstellt. Alle Bits
werden um eine oder mehr Stellen nach links verschoben. Dabei
wird von rechts her mit Nullen ergänzt. Zunächst ein (optisches)
Beispiel:
(lsh 57 1) = 114
00111001 57
--------
01110010 114
Falls wir z.B., was durchaus denkbar wäre, die Zahl 16384 einmal
nicht im Kopf parat haben sollten, aber noch wissen, dass es hier
um das 15. Bit ging, können wir auch die Zahl 1 nehmen und um
14 Bits nach links schieben:
(lsh 1 14) => 16384
Ein weiteres Beispiel ergibts sich, wenn wir (mit ActiveX-Methoden)
die AutoCAD-Hintergrundfarbe ändern möchten. Hier werden keine
ACI-Farbnummern erwartet, sondern 24-bittige Windows-Farben.
Jeweils 8 Bit sind einem der drei Farbkanäle Rot, Grün und Blau
zugeordnet. Nehmen wir einmal an, wir haben getrennt vorliegende
Farbangaben wie 50% Rot, 25% Grün, 25% Blau. Wie kann man diese
Werte in eine Frabnummer umrechnen?
Zunächst einmal müssen die Prozentangaben in Werte umgerechnet
werden:
(fix(+ 0.5(* 0.5 255))) => 128
(fix(+ 0.5(* 0.25 255))) => 64
Dann müssen wir den Rot-Wert um 16 Stellen und den Grün-Wert um
8 Stellen nach links shiften und die drei Werte addieren:
(+ (lsh 128 16) (lsh 64 8) 64) => 8405056
Es gibt zwar die Funktion
(lsh), was left shift bedeutet, in
AutoLisp, aber kein
(rsh) - für einen right shift können wir
aber jederzeit
(lsh) mit einem negativen zweiten Argument
verwenden. Bits, die dabei rechts herausfallen, verschwinden
einfach, und von links her wird immer mit Nullen aufgefüllt.
Bleiben wir bei unserem Farb-Beispiel: Um den Grünkanal einer
uns unbekanneten Windows-Farbnummer auszuwerten, verwenden wir
also diesen Ausdruck:
(logand 255 (lsh <farbe> -8))
Falls das jetzt nicht sofort einsichtig sein sollte: Zunächst
wird mit
(lsh) um 8 Stellen nach rechts verschoben, der Blaukanal
geht dabei über den Jordan. Um aber den Rot- vom Grünkanal abzutrennen,
verwenden wir auch noch
(logand 255 ...). So wird sichergestellt,
dass im Ergebnis nur die rechten 8 Bits enthalten sind. Die optische
Darstellung (wir nehmen wieder die Farbe 8405056):
10000000 01000000 01000000 8405056 ;Farbnummer
00000000 10000000 01000000 32832 ;lsh
00000000 00000000 11111111 255 ;logand
--------------------------
00000000 00000000 01000000 64 ;Ergebnis
Diese Darstellungen sollten als Grundlage bzw. als Einstieg in
die Bit-Funktionen von AutoLisp genügen. Im nächsten Kapitel werden
wir uns mit
(boole ...) befassen, einer sehr komplexen Funktion,
die das Ganze noch erweitert. Zum Schluss aber noch eine kleine,
selbstdefinierte Funktion, mit der man sich Zahlen binär ausgeben
kann. Zum Üben und Nachvollziehen ist das ungemein parktisch:
(defun prinbin(zahl / i str)
(setq i 0 str "")
(repeat 32
(setq str
(strcat str
(itoa
(logand 1(lsh zahl(- i 31)))
)
)
)
(setq i(1+ i))
(if(zerop(rem i 8))
(setq str(strcat str" "))
)
)
(strcat str ": " (itoa zahl))
)
Wir probieren die Funktion einfach ein bisschen aus:
(prinbin 255) =>
"00000000 00000000 00000000 11111111 : 255"
(prinbin 170)
"00000000 00000000 00000000 10101010 : 170"
(prinbin (+ (lsh 255 16)(lsh 255 8)255))
"00000000 11111111 11111111 11111111 : 16777215"
; das ist übrigens die Farbe 'weiss' als
; Windows-Farbnummer! Alle 3 Farbkanäle
; haben den Maximalwert 255