Warum gibt es in modernen CPUs keine "nand" -Anweisung?

52

Warum haben x86-Designer (oder auch andere CPU-Architekturen) beschlossen, es nicht einzuschließen? Es ist ein Logikgatter, mit dem andere Logikgatter erstellt werden können, daher ist es schnell wie ein einzelner Befehl. Anstatt Verkettung notund andAnweisungen (beide werden aus erstellt nand), warum keine nandAnweisung?

Amumu
quelle
20
Welchen Verwendungszweck haben Sie für den Nand-Unterricht? Wahrscheinlich x86-Designer nie gefunden
PlasmaHH
16
ARM hat die BICAnweisung, die ist a & ~b. Arm Thumb-2 hat die ORNAnweisung, die ist ~(a | b). ARM ist ziemlich modern. Das Codieren eines Befehls im CPU-Befehlssatz hat seine Kosten. Also kommen nur die "nützlichsten" in ISA an.
Eugene Sh.
24
@ Amumu Wir könnten auch ~(((a << 1) | (b >> 1)) | 0x55555555)Unterricht haben. Der Zweck wäre, dass ~(((a << 1) | (b >> 1)) | 0x55555555)statt 6 nur eine Anweisung übersetzt werden kann. Warum also nicht?
user253751
11
@ Amumu: Das ist kein usecase, und auch sein ~ nicht! Ein Verwendungszweck ist ein zwingender Grund, warum diese Anweisung nützlich ist und wo sie angewendet werden kann. Ihre Argumentation ist, als würde man sagen "Die Anweisung sollte da sein, damit sie verwendet werden kann", aber die Frage ist, "wofür sie verwendet werden soll, ist so wichtig, dass es nützlich ist, Ressourcen auszugeben".
PlasmaHH
4
Ich habe 45 Jahre lang programmiert, ein paar Compiler geschrieben und einige merkwürdige logische Operatoren verwendet, wenn verfügbar, wie z. B. IMP, aber ich hatte nie eine Verwendung für einen NAND-Operator oder eine Anweisung.
user207421

Antworten:

62

http://www.ibm.com/support/knowledgecenter/ssw_aix_61/com.ibm.aix.alangref/idalangref_nand_nd_instrs.htm : POWER hat NAND.

Aber im Allgemeinen sind moderne CPUs so aufgebaut, dass sie der automatisierten Codegenerierung durch Compiler entsprechen, und bitweises NAND wird sehr selten benötigt. Bitweises UND und ODER werden häufiger zum Bearbeiten von Bitfeldern in Datenstrukturen verwendet. Tatsächlich hat SSE AND-NOT, aber kein NAND.

Jeder Befehl hat Kosten in der Decodierlogik und verbraucht einen Opcode, der für etwas anderes verwendet werden könnte. Insbesondere bei Codierungen mit variabler Länge wie x86 können kurze Opcodes ausgehen und Sie müssen längere verwenden, was möglicherweise den gesamten Code verlangsamt.

pjc50
quelle
5
@supercat AND-NOT wird häufig zum Deaktivieren von Bits in einer Bit-Set-Variablen verwendet. egif(windowType & ~WINDOW_RESIZABLE) { ... do stuff for variable-sized windows ... }
adib
2
@adib: Ja. Ein interessantes Merkmal von "und-nicht" ist, dass im Gegensatz zum "bitweisen nicht" -Operator [~] die Ergebnisgröße keine Rolle spielt. Wenn fooes sich um ein uint64_t handelt, löscht die Anweisung foo &= ~something;manchmal mehr Bits als beabsichtigt. Wenn jedoch ein &~=Operator vorhanden wäre, könnten solche Probleme vermieden werden.
Superkatze
6
@adib Wenn WINDOW_RESIZABLEes sich um eine Konstante handelt, sollte ein Optimierer ~WINDOW_RESIZABLEzur Kompilierungszeit auswerten. Dies ist also nur ein UND zur Laufzeit.
Alephzero
4
@ MarkRansom: Nein, Ursache und Wirkung stimmen vollständig aus der Computerhistorie. Dieses Phänomen des Entwurfs von CPUs, die für Compiler anstelle von menschlichen Assembly-Programmierern optimiert sind, war Teil der RISC-Bewegung (obwohl die RISC-Bewegung selbst breiter ist als nur dieser Aspekt). Zu den für Compiler entwickelten CPUs gehören ARM und Atmel AVR. In den späten 90ern und frühen 00ern wurden Compiler-Autoren und OS-Programmierer beauftragt, CPU-Befehlssätze zu entwerfen
slebetman
3
Heutzutage sind Register-zu-Register-Operationen im Vergleich zum RAM-Zugriff im Wesentlichen kostenlos. Das Implementieren redundanter Anweisungen kostet Siliziumfläche in der CPU. Daher wird es normalerweise nur eine Form von bitweisem ODER und bitweisem UND geben, da das Hinzufügen einer bitweisen Komplement-Register-Register-Operation kaum jemals etwas verlangsamen wird.
Nigel222
31

Der Aufwand für eine solche ALU-Funktion beträgt

1) die Logik, die die Funktion selbst ausführt

2) Der Selektor, der diese Funktion auswählt, resultiert anstelle der anderen aus allen ALU-Funktionen

3) die Kosten für das Vorhandensein dieser Option im Befehlssatz (und für das Fehlen einer anderen nützlichen Funktion)

Ich stimme Ihnen zu, dass die Kosten 1) sehr gering sind. Die Kosten für 2) und 3) sind jedoch nahezu unabhängig von der Funktion. Ich denke in diesem Fall waren die 3) Kosten (die in der Anweisung belegten Bits) der Grund, diese spezielle Anweisung nicht zu haben. Bits in einer Anweisung sind eine sehr knappe Ressource für einen CPU / Architektur-Designer.

Wouter van Ooijen
quelle
29

Drehen Sie es um - sehen Sie zuerst, warum Nand im Hardware-Logik-Design beliebt war - es hat dort mehrere nützliche Eigenschaften. Dann fragen Sie, ob diese Eigenschaften in einer CPU-Anweisung noch zutreffen ...

TL / DR - tun sie nicht, es gibt also keinen Nachteil, stattdessen "And", "Or" oder "Not" zu verwenden.

Der größte Vorteil der festverdrahteten Nand-Logik war die Geschwindigkeit, die durch Verringern der Anzahl der Logikpegel (Transistorstufen) zwischen den Eingängen und Ausgängen einer Schaltung erzielt wurde. In einer CPU wird die Taktrate durch die Geschwindigkeit von viel komplexeren Operationen wie Addition bestimmt. Wenn Sie also eine AND-Operation beschleunigen, können Sie die Taktrate nicht erhöhen.

Und die Häufigkeit, mit der Sie andere Anweisungen kombinieren müssen, ist verschwindend gering - genug, damit Nand seinen Platz im Anweisungssatz wirklich nicht verdient.

Brian Drummond
quelle
1
In Fällen, in denen keine Eingangsisolation erforderlich ist, scheint "und nicht" in der Hardware sehr billig zu sein. Bereits 1977 habe ich einen Blinkerregler für den Anhänger meiner Eltern entworfen, bei dem zwei Transistoren und zwei Dioden pro Licht verwendet wurden, um eine "XOR" -Funktion auszuführen [linke Lampe == xor (linkes Signal, Bremse); rechte Lampe == xor (rechtes Signal, Bremse)], im Wesentlichen zwei und nicht Funktionen für jede Leuchte verdrahtend. Ich habe solche Tricks im LSI-Design nicht gesehen, aber ich würde denken, dass in TTL- oder NMOS-Fällen, in denen das, was auch immer einen Eingang speist, eine ausreichende Ansteuerungsfähigkeit hätte, solche Tricks Schaltkreise einsparen könnten.
Superkatze
12

Ich möchte Brian hier und Wouter und pjc50 zustimmen.

Ich möchte auch hinzufügen, dass für allgemeine Zwecke, insbesondere CISC-Prozessoren, Befehle nicht alle den gleichen Durchsatz haben - ein komplizierter Vorgang kann einfach mehr Zyklen erfordern als ein einfacher.

Betrachten Sie X86: AND(das ist eine "und" Operation) ist wahrscheinlich sehr schnell. Gleiches gilt für NOT. Lassen Sie uns einen Blick auf die Demontage werfen:

Code eingeben:

#include <immintrin.h>
#include <stdint.h>

__m512i nand512(__m512i a, __m512i b){return ~(a&b);}
__m256i nand256(__m256i a, __m256i b){return ~(a&b);}
__m128i nand128(__m128i a, __m128i b){return ~(a&b);}
uint64_t nand64(uint64_t a, uint64_t b){return ~(a&b);}
uint32_t nand32(uint32_t a, uint32_t b){return ~(a&b);}
uint16_t nand16(uint16_t a, uint16_t b){return ~(a&b);}
uint8_t nand8(uint8_t a, uint8_t b){return ~(a&b);}

Befehl zum Herstellen der Baugruppe:

gcc -O3 -c -S  -mavx512f test.c

Ausgabebaugruppe (verkürzt):

    .file   "test.c"
nand512:
.LFB4591:
    .cfi_startproc
    vpandq  %zmm1, %zmm0, %zmm0
    vpternlogd  $0xFF, %zmm1, %zmm1, %zmm1
    vpxorq  %zmm1, %zmm0, %zmm0
    ret
    .cfi_endproc
nand256:
.LFB4592:
    .cfi_startproc
    vpand   %ymm1, %ymm0, %ymm0
    vpcmpeqd    %ymm1, %ymm1, %ymm1
    vpxor   %ymm1, %ymm0, %ymm0
    ret
    .cfi_endproc
nand128:
.LFB4593:
    .cfi_startproc
    vpand   %xmm1, %xmm0, %xmm0
    vpcmpeqd    %xmm1, %xmm1, %xmm1
    vpxor   %xmm1, %xmm0, %xmm0
    ret
    .cfi_endproc
nand64:
.LFB4594:
    .cfi_startproc
    movq    %rdi, %rax
    andq    %rsi, %rax
    notq    %rax
    ret
    .cfi_endproc
nand32:
.LFB4595:
    .cfi_startproc
    movl    %edi, %eax
    andl    %esi, %eax
    notl    %eax
    ret
    .cfi_endproc
nand16:
.LFB4596:
    .cfi_startproc
    andl    %esi, %edi
    movl    %edi, %eax
    notl    %eax
    ret
    .cfi_endproc
nand8:
.LFB4597:
    .cfi_startproc
    andl    %esi, %edi
    movl    %edi, %eax
    notl    %eax
    ret
    .cfi_endproc

Wie Sie sehen können, werden bei Datentypen mit einer Größe von unter 64 einfach alle Dinge als Longs behandelt (daher das und l und nicht das l ), da dies anscheinend die "native" Bitbreite meines Compilers ist.

Die Tatsache, dass movs dazwischen liegt, ist nur auf die Tatsache zurückzuführen, dass eaxes sich um das Register handelt, das den Rückgabewert einer Funktion enthält. Normalerweise rechnen Sie einfach im ediAllzweckregister nach, um mit dem Ergebnis zu rechnen.

Für 64 Bit ist es dasselbe - nur mit "Quad" -Wörtern (also nachgestellten qWörtern) und rax/ rsianstelle von eax/ edi.

Es scheint, dass Intel für 128-Bit-Operanden und größere keine "Nicht" -Operation implementieren wollte. Stattdessen erstellt der Compiler ein All- 1Register (Selbstvergleich des Registers mit sich selbst, im Register gespeichertes Ergebnis mit der vdcmpeqdAnweisung) und xorso weiter.

Kurz gesagt: Durch die Implementierung einer komplizierten Operation mit mehreren elementaren Anweisungen wird die Operation nicht unbedingt verlangsamt. Es ist einfach kein Vorteil, wenn eine Anweisung mehrere Anweisungen ausführt, wenn sie nicht schneller ist.

Marcus Müller
quelle
10

Erstens, verwechseln Sie nicht bitweise und logische Operationen.

Bitweise Operationen werden normalerweise verwendet, um Bits in Bitfeldern zu setzen / löschen / umzuschalten / zu prüfen. Keine dieser Operationen erfordert nand ("and not", auch als "bit clear" bekannt, ist nützlicher).

Logische Verknüpfungen in den meisten modernen Programmiersprachen werden mit Kurzschlusslogik ausgewertet. Daher ist in der Regel ein branchenspezifischer Ansatz für die Implementierung erforderlich. Selbst wenn der Compiler feststellen kann, dass die Kurzschluss- / Gesamtauswertung keinen Unterschied zum Programmverhalten macht, sind die Operanden für die logischen Operationen normalerweise nicht in einer geeigneten Form, um den Ausdruck mit den bitweisen asm-Operationen zu implementieren.

Peter Green
quelle
10

NAND wird häufig nicht direkt implementiert, da die AND-Anweisung implizit die Möglichkeit bietet, auf eine NAND-Bedingung zu springen.

Das Ausführen einer logischen Operation in einer CPU setzt häufig Bits in einem Flagregister.

Die meisten Flag-Register haben ein ZERO-Flag. Das Null-Flag wird gesetzt, wenn das Ergebnis einer logischen Operation Null ist, und ansonsten gelöscht.

Die meisten modernen CPUs haben einen Sprungbefehl, der springt, wenn das Null-Flag gesetzt ist. Sie haben auch eine Anweisung, die springt, wenn das Null-Flag nicht gesetzt ist.

AND und NAND sind Ergänzungen. Wenn das Ergebnis einer AND-Operation Null ist, ist das Ergebnis einer NAND-Operation 1 und umgekehrt.

Wenn Sie also nicht springen möchten, wenn das NAND zweier Werte wahr ist, führen Sie einfach die UND-Operation aus und springen, wenn das Null-Flag gesetzt ist.

Wenn Sie also nicht springen möchten, wenn das NAND zweier Werte falsch ist, führen Sie einfach die UND-Operation aus und springen, wenn das Null-Flag gelöscht ist.

user4574
quelle
In der Tat gibt Ihnen die Wahl der bedingten Sprunganweisung die Wahl zwischen invertierender und nicht invertierender Logik für eine ganze Klasse von Operationen, ohne diese Wahl für jede einzelne implementieren zu müssen.
Chris Stratton
Dies hätte die beste Antwort sein sollen. Die Null-Flag-Operationen machen NAND für logische Operationen überflüssig, da AND + JNZ und AND + JZ im wesentlichen kurzgeschlossen sind / logisches AND und NAND jeweils die gleiche Anzahl von Operationscodes annehmen.
Lie Ryan
4

Nur weil etwas billig ist, heißt das nicht, dass es kostengünstig ist .

Wenn wir Ihre Argumentation ad absurdum nehmen, würden wir zu dem Schluss kommen, dass eine CPU größtenteils aus Hunderten von Varianten von NOP-Befehlen bestehen sollte - weil sie am billigsten zu implementieren sind.

Oder vergleichen Sie es mit Finanzinstrumenten: Würden Sie eine Anleihe im Wert von 1 USD mit einer Rendite von 0,01% kaufen, nur weil Sie dies können? Nein, Sie sparen lieber diese Dollars, bis Sie genug haben, um eine 10-Dollar-Anleihe mit besserer Rendite zu kaufen. Gleiches gilt für das Silikonbudget einer CPU: Es ist effektiv, viele billige, aber nutzlose Operationen wie NAND zu eliminieren und die gesparten Transistoren in etwas teureres, aber wirklich nützliches zu verwandeln.

Es gibt kein Rennen, um so viele Operationen wie möglich zu haben. Wie RISC gegen CISC bewiesen hat, was Turing von Anfang an wusste: Weniger ist mehr. Es ist eigentlich besser, so wenige Operationen wie möglich zu haben.

Agent_L
quelle
nopkann nicht alle anderen Logikgatter implementieren, aber nandoder norkann effektiv jeden Befehl neu erstellen, der in einer CPU in Software implementiert ist. Wenn wir den RISC-Ansatz
wählen
@ Amumu Ich denke, Sie mischen gateund instruction. Gates werden verwendet, um Anweisungen zu implementieren, nicht umgekehrt. NOPist eine Anweisung, kein Tor. Und ja, CPUs enthalten Tausende oder sogar Millionen von NAND-Gattern, um alle Anweisungen zu implementieren. Nur nicht die "NAND" -Anweisung.
Agent_L
2
@Amumu Das ist nicht der RISC-Ansatz :) Das ist der Ansatz "Verwenden Sie die breitesten Abstraktionen", der außerhalb sehr spezifischer Anwendungen nicht allzu nützlich ist. Sicher, nandist ein Tor, das verwendet werden kann, um andere Tore zu implementieren; aber du hast schon alle anderen anweisungen . Das erneute Implementieren mithilfe einer nandAnweisung wäre langsamer . Und sie werden viel zu oft verwendet, um dies zu tolerieren, im Gegensatz zu Ihrem speziellen Beispiel, bei dem nandkürzere Codes erzeugt werden (nicht schnellerer Code, nur kürzerer). Aber das ist extrem selten und der Nutzen ist die Kosten einfach nicht wert.
Luaan
@ Amumu Wenn wir Ihren Ansatz verwenden würden, hätten wir keine Positionsnummern. Was ist der Sinn, wenn Sie ((((()))))statt 5 einfach sagen können , oder? Fünf ist nur eine bestimmte Zahl, das ist viel zu einschränkend - Sätze sind viel allgemeiner: P
Luaan
@Agent_L Ja, ich weiß, dass Gates Anweisungen implementieren. nandimplementiert alle Gates, kann also implizit nandalle anderen Befehle implementieren. Wenn ein Programmierer dann eine nandAnweisung zur Verfügung hat, kann er seine eigenen Anweisungen erfinden, wenn er in Logikgattern denkt. Was ich von Anfang an gemeint habe, ist, dass ein Programmierer, wenn es so grundlegend ist, warum ihm keine eigene Anweisung gegeben wurde (dh ein Opcode in der Decoderlogik), eine solche Anweisung verwenden kann. Nachdem ich eine Antwort erhalten habe, weiß ich natürlich, dass dies von der Verwendung der Software abhängt.
Amumu
3

Auf Hardwareebene ist weder nand noch nor die elementare logische Verknüpfung. Abhängig von der Technologie (oder abhängig davon, was Sie willkürlich 1 und was Sie 0 nennen), kann entweder nand oder nor auf sehr einfache, elementare Weise implementiert werden.

Wenn wir den "nor" -Fall ignorieren, wird die gesamte andere Logik aus nand konstruiert. Aber nicht , weil es einige Computer wissenschaftlicher Beweis ist , dass alle logischen Operationen aus und konstruiert werden können - der Grund ist , dass es einfach ist , nicht jede elementare Methode xor zu bauen, oder, und etc. , das besser ist , dann ist es aus Nands Konstruktion.

Bei Computeranweisungen ist die Situation anders. Ein nand-Befehl könnte implementiert werden und wäre ein kleines bisschen billiger als beispielsweise die Implementierung von xor. Aber nur ein kleines bisschen, denn die Logik, die das Ergebnis berechnet, ist winzig im Vergleich zu der Logik, die den Befehl dekodiert, Operanden verschiebt, sicherstellt, dass nur eine Operation berechnet wird, und das Ergebnis aufnimmt und an die richtige Stelle liefert. Die Ausführung jedes Befehls dauert einen Zyklus. Dies entspricht einer Addition, die logisch zehnmal komplizierter ist. Die Einsparungen von nand gegenüber xor wären vernachlässigbar.

Was dann zählt, ist, wie viele Anweisungen für Operationen benötigt werden , die tatsächlich von typischem Code ausgeführt werden . Nand befindet sich bei weitem nicht ganz oben auf der Liste der häufig angeforderten Operationen. Es ist viel üblicher, dass und oder nicht angefordert werden. Entwickler von Prozessoren und Anweisungssätzen untersuchen viele vorhandene Codes und ermitteln, wie sich unterschiedliche Anweisungen auf diesen Code auswirken. Sie stellten höchstwahrscheinlich fest, dass das Hinzufügen eines nand-Befehls zu einer sehr geringen Verringerung der Anzahl von Prozessorbefehlen führen würde, die ausgeführt werden, um typischen Code auszuführen, und das Ersetzen eines vorhandenen Befehls durch nand die Anzahl der ausgeführten Befehle erhöhen würde.

gnasher729
quelle
2

Nur weil NAND (oder NOR) alle Gatter in kombinatorischer Logik implementieren kann, lässt sich dies nicht auf die gleiche Weise in einen effizienten bitweisen Operator übersetzen. Um ein UND nur mit NAND-Operationen zu implementieren, wobei c = a UND b ist, müsste c = a NAND b sein, dann b = -1, dann c = c NAND b (für ein NICHT). Die grundlegenden logischen bitweisen Operationen sind AND, OR, EOR, NOT, NAND und NEOR. Das ist nicht viel, und die ersten vier sind in der Regel sowieso eingebaut. In der Kombinationslogik sind die grundlegenden Logikschaltungen nur durch die Anzahl der verfügbaren Tore begrenzt, was ein völlig anderes Ballspiel ist. Die Anzahl der möglichen Verbindungen in einem programmierbaren Gate-Array, nach denen Sie wirklich suchen, ist in der Tat sehr hoch. Einige Prozessoren haben tatsächlich Gate-Arrays eingebaut.

Robin Hodson
quelle
0

Sie implementieren ein Logikgatter nicht, nur weil es funktionsfähig ist, insbesondere wenn die anderen Logikgatter nativ verfügbar sind. Sie implementieren, was von Compilern am häufigsten verwendet wird.

NAND, NOR und XNOR werden sehr selten benötigt. Neben den klassischen bitweisen Operatoren AND, OR und XOR hätte nur ANDN ( ~a & b) - das nicht NAND ( ~(a & b)) ist - einen praktischen Nutzen. Wenn überhaupt, sollte eine CPU dies implementieren (und tatsächlich implementieren einige CPUs ANDN).

Stellen Sie sich zum Erläutern des praktischen Nutzens von ANDN vor, Sie hätten eine Bitmaske, die viele Bits verwendet, aber Sie interessieren sich nur für einige der folgenden:

enum my_flags {
    IT_IS_FRIDAY = 1,
    ...
    IT_IS_WARM = 8,
    ...
    THE_SUN_SHINES = 64,
    ...
};

Normalerweise möchten Sie in der Bitmaske überprüfen, ob Ihre Bits von Interesse sind

  1. Sie sind alle eingestellt
  2. Mindestens einer ist gesetzt
  3. Mindestens einer ist nicht gesetzt
  4. Keiner ist gesetzt

Fangen wir an, indem wir Ihre Interessen zusammenfassen:

#define BITS_OF_INTEREST (IT_IS_FRIDAY | IT_IS_WARM | THE_SUN_SHINES)

1. Alle Bits von Interesse werden gesetzt: bitweises ANDN + logisches NOT

Nehmen wir an, Sie möchten wissen, ob Ihre Interessen in Ordnung sind. Sie können es so sehen (my_bitmask & IT_IS_FRIDAY) && (my_bitmask & IT_IS_WARM) && (my_bitmask & THE_SUN_SHINES). Jedoch normalerweise würden Sie das in zusammenbrechen

unsigned int life_is_beautiful = !(~my_bitmask & BITS_OF_INTEREST);

2. Mindestens ein Bit von Interesse ist gesetzt: bitweises UND

Nehmen wir nun an, Sie möchten wissen, ob mindestens ein Bit von Interesse gesetzt ist. Sie können es als sehen (my_bitmask & IT_IS_FRIDAY) || (my_bitmask & IT_IS_WARM) || (my_bitmask & THE_SUN_SHINES). Jedoch normalerweise würden Sie das in zusammenbrechen

unsigned int life_is_not_bad = my_bitmask & BITS_OF_INTEREST;

3. Mindestens ein Bit von Interesse ist nicht gesetzt: Bitweises ANDN

Angenommen, Sie möchten wissen, ob mindestens ein Bit von Interesse nicht gesetzt ist. Sie können es als sehen !(my_bitmask & IT_IS_FRIDAY) || !(my_bitmask & IT_IS_WARM) || !(my_bitmask & THE_SUN_SHINES). Jedoch normalerweise würden Sie das in zusammenbrechen

unsigned int life_is_imperfect = ~my_bitmask & BITS_OF_INTEREST;

4. Es ist kein interessierendes Bit gesetzt: Bitweises UND + logisches NICHT

Nun lassen Sie uns sagen , dass Sie , wenn alle Bits von Interesse wissen wollen , sind nicht festgelegt. Sie können es als sehen !(my_bitmask & IT_IS_FRIDAY) && !(my_bitmask & IT_IS_WARM) && !(my_bitmask & THE_SUN_SHINES). Jedoch normalerweise würden Sie das in zusammenbrechen

unsigned int life_is_horrible = !(my_bitmask & BITS_OF_INTEREST);

Dies sind die üblichen Operationen, die mit einer Bitmaske ausgeführt werden, sowie das klassische bitweise ODER und XOR. Ich glaube aber , dass eine Sprache (was nicht ist eine CPU ) sollte das bitweise NAND umfassen, NOR und XNOR Operatoren (deren Symbole wäre ~&, ~|und ~^), obwohl nur selten verwendet. Ich würde nicht den ANDN Operator in einer Sprache , obwohl enthalten, da es nicht kommutativ ist ( a ANDN bist nicht das gleiche wie b ANDN a) - besser zu schreiben ~a & bstatt a ANDN b, die ehemaligen zeigt deutlicher die asimmetry der Operation.

Madmurphy
quelle