Ein paar Worte vorabHome   Letzte MeldungenNews   Index der Kapitel und der besprochenen FunktionenIndex   Wer ich bin, warum ich diese Seiten mache, KontaktImpressum   Ich freue mich über jeden Eintrag im Gästebuch!Gästebuch   Einige Links zu anderen AutoLisp-SeitenLinks   Copyrights und DisclaimerRechts
Hier können die kompletten Seiten als ZIP-File heruntergeladen werden!

Berechnen von arithmetischen Ausdrücken in der Kommandozeile Sitz!Platz!Fass!
Das Verschachteln von Ausdrücken Alte Schachtel!
Das Speichern von Werten in Variablen Gebunkert
Verhindern der Evaluation mit Quote Bergbäche
Erzeugen von einfachen Listen in AutoLisp Brot,Eier,Käse
Einfache Funktionen zur Listenbearbeitung ...um die Wurst
Funktionen für den Zugriff auf Listenelemente Was ein Salat!
Über Haupt- und Nebeneffekte von Funktionen Schwer schuften
Das Definieren von eigenen Funktionen in AutoLisp Ostfriesischer...
Lokale Variablen und Funktionsargumente in AutoLisp Kondome!
Das Laden von Programmdateien in AutoLisp Banküberfall
Verzweigung in Funktionen aufgrund von Entscheidungen Wenn das Wort...
Zusammenfassen von Entscheidungen mit den Logik-Funktionen Ins Schweinderl
Mehrfach-Verzweigungen in AutoLisp mit Cond 3. Strasse links
Schleifen zum Steuern des Ablaufs in AutoLisp-Funktionen Wie im Fernsehen
Testfunktionen zum Steuern von Schleifen in AutoLisp Schwanger?
Gleichheitstests als Schleifenkriterium in AutoLisp Noch gleicher?
Zeichneketten-Bearbeitung in AutoLisp Rauchzeichen
Funktionen zur Konvertierung von Datentypen in AutoLisp Wasser zu Wein
Komplexere Funktionen für die Bearbeitung von Listen in AutoLisp Nicht arbeiten...
Das Erzeugen von anonymen Funktionen mit lambda Schwarze Kutte
Das Bearbeiten von Listenelementen mit foreach Jedem das Seine
Erzeugen und Verwenden von Assoziationslisten in AutoLisp Beim Psychiater
Zugriff auf Geometriedaten und Erzeugen von Geometrieelementen Ententanz
Der Umgang mit Auswahlsätzen in AutoLisp Jung gefreit, ...
Auswahl von AutoCAD-Zeichnungsgeometrie mit ssget Raffgierig!
Verändern von Zeichnungs-Geometrie mit entmod Flickschusterei
Das Erzeugen von Geometrie mit entmake Houdini
Vom Umgang mit Bits in AutoLisp, Teil 1 Prinzessin auf...
Vom Umgang mit Bits in AutoLisp, Teil 2 Franzosen
Über Programmierstile in AutoLisp, Teil 1 Emma
Über Programmierstile in AutoLisp, Teil 2 Sti(e)lblüten
Über Programmierstile in AutoLisp, Teil 3 Eingewickelt
Über Programmierstile in AutoLisp, Teil 4 Doofe Frisur?


Zum den Seiten für Fortgeschrittene

Zu den ActiveX-Seiten

Meine Private HP mit Fotos, Gedichten, Musik und Postkartenversand

Mein Online-Lexikon der Fotografie

Mein völlig abgedrehtes Reisebüro










Eigentlich haben wir ja im vorigen Kapitel schon alles gelernt, was man über den Umgang mit Bits in AutoLisp wissen sollte. Trotzdem kommt hier noch ein Kapitel zu diesem Thema, denn es gibt da noch die Funktion (boole ...), an der sich schon viele AutoLisp-Einsteiger die Zähne ausgebissen haben. Die AutoLisp-Onlinehilfe hält sich zu diesem Thema allerdings auch sehr zurück: Wer nicht sowieso schon Bescheid weiss, wird aus der Hilfe sowieso nicht schlau.

Ich werde versuchen, dass Thema hier so anfängerfreundlich wie möglich zu erörtern. Es geht allerdings auch nicht ohne ein paar grundlegende Kenntnisse der Mathematik bzw. Logik - aber vielleicht reicht auch durchaus ein gewisses Vorstellungs- bzw. Abstraktionsvermögen. Wir werden sehen - und wer auch trotz meiner gut gemeinten Bemühungen hier nicht durchsteigt, der sei damit getröstet, dass man auch ohne diese Funktion gute Lisp-Programme schreiben kann. (boole ...) ist eine schöne Funktion für Programme, in denen komplexe Logik eine Rolle spielt. Aber meistens braucht man sie überhaupt nicht.

Die (boole)-Funktion bekommt immer drei Argumente. Das erste Argument ist eine Art Steuercode, es muss eine Zahl zwischen 0 und 15 sein. Die beiden anderen Argumente sind dann zwei Ganzzahlen, wie wir es schon von (logand) und (logior) kennen. Diese beiden Funktionen, die die Logik-Funktionen AND und OR repräsentieren, sind aber nur zwei Möglichkeiten, wie man zwei Zahlen bitweise miteinander verknüpfen kann.

Insgesamt existieren 16 solcher Möglichkeiten, und es wird nicht überraschen, dass der Steuercode von 0 bis 15 genau diese Möglichkeiten widerspiegelt. Um es gleich vorweg zu nehmen: AND hat den Code 1, und OR hat den Code 7. Wir vergleichen mit (logand) und (logior) und benutzen dazu unsere selbstdefinierte Funktion (prinbin) aus dem vorigen Kapitel, um die Bits wieder 'persönlich' inspizieren zu können:
(prinbin 170) =>
"00000000 00000000 00000000 10101010 : 170"

(prinbin 118) =>
"00000000 00000000 00000000 01110110 : 118"

(prinbin(logand 170 118)) =>
"00000000 00000000 00000000 00100010 : 34"

(prinbin(boole 1 170 118)) =>
"00000000 00000000 00000000 00100010 : 34"

(prinbin(logior 170 118)) =>
"00000000 00000000 00000000 11111110 : 254"

(prinbin(boole 7 170 118))
"00000000 00000000 00000000 11111110 : 254"
                  
Wir sehen, dass (boole 1 ...) identisch mit (logand) ist, und dass (boole 7 ...) das selbe tut wie (logior). Bleibt also eigentlich nur noch zu klären, wozu die anderen 14 Steuercodes gut sein sollen. Aber das ist nicht so ganz einfach... Untersuchen wir doch mal den Steuercode 2 (die uninteressanten Aufrufe von (prinbin) lasse ich jetzt einfach weg):
"00000000 00000000 00000000 10101010 : 170"
"00000000 00000000 00000000 01110110 : 118"

(prinbin(boole 2 170 118)) =>
"00000000 00000000 00000000 10001000 : 136"
                  
Es scheint so, als würden hier Bits gesetzt, wenn in der ersten Zahl eine 1 vorliegt, in der zweiten aber eine 0. Eine kleine Gegenprobe scheint das zu bestätigen:
(prinbin(boole 2 170 255)) =>
"00000000 00000000 00000000 00000000 : 0"
                  
Es ist so, und wir können den Zusammenhang doch einmal etwas mehr auf den Punkt bringen: Wenn wir das Bit der ersten Zahl immer als A bezeichnen und das zweite mit B, dann haben wir hier den Fall A AND not B. Das kürzen wir dann noch ein bisschen ab, in dem wir es so schreiben: A AND ~B.

Wir könnten jetzt auf die Idee kommen, vielleicht den Steuercode 0 zu untersuchen, oder auch den Code 3. Die Untersuchungen zu Code 0 sparen wir uns hier - denn (boole 0 ... ...) gibt immer die Zahl 0 als Ergebnis zurück. Das mag für Theoretiker hochinteressant sein, aber nicht für uns. Aber auf den Steuercode 3 können wir doch mal ein Auge riskieren:
"00000000 00000000 00000000 10101010 : 170"
"00000000 00000000 00000000 01110110 : 118"

(prinbin(boole 3 170 118)) =>
"00000000 00000000 00000000 10101010 : 170"
                  
Hmmm, das bringt uns erst einmal nicht viel weiter... Vielleicht lag's an den Zahlen? Wir versuchen es einfach mal mit zwei anderen Zahlen:
"00000000 00010011 00110000 10110101 : 1257653"
"00000000 01100101 01001011 10111000 : 6638520"

(prinbin(boole 3 1257653 6638520)) =>
"00000000 00010011 00110000 10110101 : 1257653"
                  
Nicht besonders aufregend, oder? Wir bekommen immer A zurück. Aber die Ursache ist schnell aufgeklärt: Steuercode 3 ist einfach die Kombination von Code 1 und Code 2, denn auch hier wird binär gearbeitet. Tatsächlich kennt (boole) nur die Codes 0, 1, 2, 4 und 8. Alle anderen Steuercodes sind Kombinationen. Wenn wir das Ergebnis ein wenig näher untersuchen, können wir folgende Wahrheitstafeln aufstellen:
A B | (A AND B)    A B | (A AND ~B)
----+----------    ----+---------
0 0 |     0        0 0 |     0
0 1 |     0        0 1 |     0
1 0 |     0        1 0 |     1
1 1 |     1        1 1 |     0

(A AND B)(A AND ~B)|(A AND B) OR (A AND ~B)
-------------------+-----------------------
    0        0     |           0
    0        0     |           0
    0        1     |           1
    1        0     |           1
                  
Natürlich herrscht hier doch noch ein wenig Erklärungsbedarf, insbesondere darüber, woher das OR kommt, das die Ergebnisse der Steuercodes 1 und 2 verknüpft. Tja, ich habe es einfach hingeschrieben, und wer sich die untere Wahrheitstafel in Ruhe anschaut, wird feststellen, dass auch gar nichts anderes dort hineinpassen würde!

Jetzt muss es sein: Den Ausdruck '(A AND B) OR (A AND ~B)' kann man umformen - er wird zu 'A AND (B OR ~B)'. Streng nach den Regeln der Prädikatenlogik betrachtet, haben wir hier das Distributivgesetz angewendet. Interpretieren wir den Teil '(B OR ~B)', was irgendwie doch recht starke und durchaus berechtigte Assoziationen zu Shakespeare erweckt, was aber wiederum überhaupt nichts mit dem zu tun hat, was die Philosophen unter dem Assoziativgesetz (ja, die Prädikatenlogik ist eher eine Domäne der Philosophie als der Mathematik)...

Ich fange lieber noch mal von vorne an: B ODER NICHT B bedeutet einfach: Es ist völlig Wurst, was mit B ist. Nur A zählt, und das haben wir ja von Anfang an gewusst! Nach dem Absorptionsgesetz können wir jetzt den Ausdruck rigoros auf 'A' zusammenstreichen:
(A AND B) OR (A AND ~B) ; Originalausdruck
A AND (B OR ~B)         ; Distributivgesetz
A                       ; Absorptionsgesetz
                  
Den Steuercode 3 können wir uns also dahin tun, wo sich auch schon der Code befindet: Uninteressant. Schauen wir uns lieber den Code 4 an, jetzt allerdings in verkürzter Darstellung meinerseits:
"00000000 00010011 00110000 10110101 : 1257653"
"00000000 01100101 01001011 10111000 : 6638520"

(prinbin(boole 4 1257653 6638520)) =>
"00000000 01100100 01001011 00001000 : 6572808"
                  
Da wir nun schon echte Meister im Erkennen binärer Zusammenhänge sind, sehen wir sofort, dass hier der Fall '~A AND B' vorliegt, Steuercode 2 spiegelverkehrt sozusagen. Code 5 ist dann (logischerweise) '(A AND B) OR (~A AND B)', was wir ruckzuck unter Anwendung von Distributiv- und Absorptionsgesetz zu 'B' und sonst gar nichts zusammenstauchen. Ein kurzer Test zeigt, dass das zu Recht geschah:
(prinbin(boole 5 1257653 6638520)) =>
"00000000 01100101 01001011 10111000 : 6638520"
                  
Fall 6 stellt sich nun so dar: Da es sich hier um die Kombination von 2 und 4 handelt, haben wir es mit dem Ausdruck '(A AND ~B) OR (~A AND B)' zu tun. Hier stellt sich eine echte Überraschung ein: Wir können das nicht kürzen, aber es ist wirklich eine interessante Kombination! Hier handelt es sich nämlich um das Entweder-Oder, mit dem wir bisher noch gar nichts zu tun hatten. Dieses XOR spielt übrigens in der Praxis eine genausogrosse Rolle wie AND und OR.

Wer sich einmal die (entget)-Liste, die Geometriedaten also, eines ACIS-3D-Solids in AutoCAD angeschaut hat, war sicherlich verwundert über den offensichtlichen Datenmüll. AutoDESK verschlüsselt diese Daten, offensichtlich aus lizenzrechtlichen Gründen. Die XOR-Funktion wird seit jeher für eine primitive Verschlüsselung verwendet, die den Vor- bzw. Nachteil (je nach Standpunkt des Verschlüsselers oder desjenigen, der den Schlüssel knacken möchte) hat, dass eine zweite XOR-Anwendung die Verschlüsselung wieder aufhebt. Eine kurze Praxiseinlage:
"00000000 00010011 00110000 10110101 : 1257653"
"00000000 01100101 01001011 10111000 : 6638520"

(prinbin(boole 6 1257653 6638520)) =>
"00000000 01110110 01111011 00001101 : 7764749"

; die Rückentschlüsselung:
(prinbin(boole 6 7764749 6638520)) =>
"00000000 00010011 00110000 10110101 : 1257653"

; und noch eine:
(prinbin(boole 6 1257653 7764749)) =>
"00000000 01100101 01001011 10111000 : 6638520"
                  
An dieser Stelle wird auch klar, dass die Reihenfolge der beiden letzten Argumente, der Zahlen also, bei manchen Steuercodes eine Rolle spielt, bei anderen aber nicht. Bei Code 6 spielt es jedenfalls keine Rolle, ob wir die Zahlen vertauschen - ebensowenig wie bei 0 oder 1. Bei zwei bzw. 4 ist das anders: Ein Tausch der Argumente macht aus Code 2 den Code 4 und umgekehrt.

Kommen wir nun zum komplizierten Fall 7, von dem wir ja schon wissen, dass es sich um das OR handelt. Die Kombination von 1, 2 und 4 ergibt folgenden Ausdruck: '(A AND ~B) OR (~A AND B) OR (A AND B)'. In Worten heisst das: Entweder A, aber nicht B, oder auch nicht A, aber B, oder auch sowohl A als auch B. Diese umständliche Formulierung ergibt sich aus der internen Logik der (boole)-Funktion, und sie lässt sich ohne Bedenken zu '(A OR B)' umformen. Den Beweis bleibe ich allerdings einfach schuldig...

Wer bisher noch nicht 'ausgestiegen' ist, hat nun allen Grund dazu: Die zweite Hälfte mit den Codes 8 bis 15 wird noch wesentlich unangenehmer! Code 8 ist für das WEDER-NOCH zuständig, das in der Praxis auch sehr nützlich ist. Allerdings kommt hier ein unangenehmes Detail ins Spiel: Wir testen den Code 8 einfach mal:
"00000000 00010011 00110000 10110101 : 1257653"
"00000000 01100101 01001011 10111000 : 6638520"

(prinbin(boole 8 1257653 6638520)) =>
"11111111 10001000 10000100 01000010 : -7830462"
                  
Wie man sieht, ist das Ergebnis eine negative Zahl! Das 'unangenehme Detail' nennt sich übrigens 'Zweierkomplement' und bezeichnet die Art und Weise, wie negative Ganzzahlen im Rechner verarbeitet werden. Um etwas Einblick zu gewinnen, lassen wir uns die Zahlen 2, 1, 0, -1, -2, -3 binär ausgeben:
"00000000 00000000 00000000 00000010 :  2"
"00000000 00000000 00000000 00000001 :  1"
"00000000 00000000 00000000 00000000 :  0"
"11111111 11111111 11111111 11111111 : -1"
"11111111 11111111 11111111 11111110 : -2"
"11111111 11111111 11111111 11111101 : -3"
                  
Ein paar Erklärungen: Negativ sind Zahlen dann, wenn das höchstwertige Bit gesetzt ist, also das ganz linke, oder - da wir heutzutage mit 32-Bit-Systemen arbeiten, das 32. Bit von rechts. Würde man die Sache so handhaben, dass man dieses Bit als Vorzeichenbit verwendet und die anderen 31 Bit immer identisch als Werte-Bits, dann hätten wir diesen Fall:
; so sieht es in Wirklichkeit nicht aus!
"0 0000000 00000000 00000000 00000010 : + 2"
"0 0000000 00000000 00000000 00000001 : + 1"
"0 0000000 00000000 00000000 00000000 : + 0"
"1 0000000 00000000 00000000 00000000 : - 0 "
"1 0000000 00000000 00000000 00000001 : - 1 "
"1 0000000 00000000 00000000 00000010 : - 2 "
                  
Nun, da sind zwei Nullen drin! Es gibt aber kein +0 oder -0, die Null ist immer vorzeichenlos. Das würde jeden Prozessor völlig durcheinanderbringen! Es muss also korrigiert werden:
; und so auch nicht - nur ein Modell!
"0 0000000 00000000 00000000 00000010 : + 2"
"0 0000000 00000000 00000000 00000001 : + 1"
"0 0000000 00000000 00000000 00000000 : + 0"
"1 0000000 00000000 00000000 00000000 : - 1 "
"1 0000000 00000000 00000000 00000001 : - 2 "
"1 0000000 00000000 00000000 00000010 : - 3 "
                  
So stellt sich zwar eine unschöne Asymmetrie ein, aber die muss man zugunsten der eindeutigen Null in Kauf nehmen. Die Realität sieht aber noch anders aus: In der Zweierkomplement-Darstellung werden alle Bits negiert. Hier noch einmal, wie es tatsächlich gehandhabt wird:
"00000000 00000000 00000000 00000010 :  2"
"00000000 00000000 00000000 00000001 :  1"
"00000000 00000000 00000000 00000000 :  0"
"11111111 11111111 11111111 11111111 : -1"
"11111111 11111111 11111111 11111110 : -2"
"11111111 11111111 11111111 11111101 : -3"
                  
Diese Darstellungsweise hat gegüber den beiden letzten Modellen den Vorteil, dass der Prozessor bei Additionen keine Rücksicht darauf nehmen muss, ob eine negative oder positive Zahl vorliegt - der Rechenweg bleibt der Gleiche! Mehr möchte ich zum Thema Zweierkomplement jetzt auch nicht mehr sagen - dieses Kapitelist sowieso schon zu lang!

Noch einmal zurück zur (boole)-Funktion: Das NOR (Weder-Noch) setzt also da ein Bit, wo weder A noch B eines haben. Bei zwei positiven Zahlen, die ja beide eine Null als MSB (most significant bit, das linke also) haben, führt das zwangsläufig zu einem negativen Ergebnis.

Wir sollten jetzt aber doch noch einmal etwas systematischer an die (boole)-Funktion herangehen. Alle sechzehn Möglichkeiten dieser Funktion lassen sich in einer großen Wahrheitstafel darstellen - un plötzlichwird alles ganz einfach und logisch. In der AutoLisp-Hilfe finden wir ja schon einen Ansatz von Dokumentation, nämlich eine kleine Wahrheitstafel für die (boole)-Funktion selbst:
Int1 Int2 | Operator bit
 0    0   |      8
 0    1   |      4
 1    0   |      2
 1    1   |      1
                  
Was uns noch fehlt, ist nur ein Ansatz, wie diese kleine Tabelle zu interpretieren ist: Es handelt sich einfach um eine Aufschlüsselung, wann was negiert wird. Code 1 negiert also nichts, Code 2 negiert die Bits in Zahl 2, Code 4 in Zahl 1 und Code 8 in beiden. Mit diesem Wissen ausgestattet, sehen wir nun, dass die große Wahrheitstafel ganz einfach aufgebaut ist.

Nicht notwendig, aber vielleicht für den einen oder anderen ganz nützlich ist das Wissen um die 'wissenschaftlichen Namen', um die ich die Tabelle erweitert habe. Ich erwähne diese also nur für alle Fälle...
A     0 0 1 1
B     0 1 0 1
-------------

0     0 0 0 0     Nullfunktion

1     0 0 0 1     Konjunktion (A AND B)

2     0 0 1 0     2. Inhibition (A AND ~B)

3     0 0 1 1     1. Identität (A)

4     0 1 0 0     1. Inhibition (~A AND B)

5     0 1 0 1     2. Identität (B)

6     0 1 1 0     Antivalenz (A >-< B) = (A XOR B)
                            = ((~A AND B) OR (A AND ~B))

7     0 1 1 1     Disjunktion (A OR B)

8     1 0 0 0     Peirce-Funktion (A NOR B) = ~(A OR B)

9     1 0 0 1     Äquivalenz (A <=> B)

10    1 0 1 0     2. Negation (~B)

11    1 0 1 1     2. Implikation (A OR ~B)

12    1 1 0 0     1. Negation (~A)

13    1 1 0 1     1. Implikation (~A OR B)

14    1 1 1 0     Sheffer-Funktion (A NAND B) = ~(A AND B)

15    1 1 1 1     Einsfunktion
                  
Leider konnte (und wollte) ich das hier nicht zu wissenschaftlich betreiben. Insbesondere bei der verwendung von Zeichen war ich auf das beschränkt, was sich als ASCII-Text darstellen lässt. Das Ganze noch als Grafik darzustellen, erschien mir dann doch zu viel des Guten. Ich denke aber, dass es auch trotz kleiner formaler Mängel seinen Zweck erfüllt.

Wer tapfer bis hierher durchgehalten hat und nun meint, er könne endlich aufatmen: Nein. Dieses Thema geht weiter! Die (boole)-Funktion ist enträtselt, aber zum Handwerkszeug eines AutoLisp-Programmierers gehört auch eine Portion Wissen im Zusammenhang mit der Logik. Das Distributiv- oder auch das Absorptionsgesetz lassen sich nämlich z.B. auch auf verschachtelte (if(and(not....(or...(not...-Ausdrücke anwenden, und die DeMorganschen Regeln halte ich da für geradezu unverzichtbar! Und ist es nicht ein verlockendes Ziel, aus einem seitenlangen Wust von ifs und ands und ors im Handumdrehen eine kleine, übersichtliche Funktion machen zu können?

Techniken wie das Karnaugh-Veitch-Diagramm machen das möglich. Ob sich dieses Kapitel dann hier anschliessen wird oder auf die Fortgeschrittenen-Seiten wandert, werde ich erst entscheiden, wenn es fertig ist.