Wie werden negative Zahlen dargestellt -- Vorzeichen-Betrag, Einerkomplement, Zweierkomplement

In dieser Lektion wirst du verstehen: warum die Darstellung negativer Zahlen in Computern nicht so einfach ist wie das "Hinzufügen eines Minuszeichens", und wie das Zweierkomplement all diese Probleme auf clevere Weise löst.


Alltagsanalogie: Nummerierung von Hotel-Etagen

Stell dir ein Hotel mit 256 Etagen vor.

Wenn man mit natürlichen Zahlen nummeriert, kann man nur 0 bis 255 verwenden.

Aber was, wenn wir von "128 Etagen unter der Erde" bis "127 Etagen über der Erde" nummerieren wollen?

Ein cleverer Ansatz: Interpretiere die Zahlen 128~255 als -128~-1 neu.

Die Zahl 128 wird zu -128, die Zahl 255 wird zu -1, und die Zahl 0 bleibt 0.

Dies istKernidee des Zweierkomplements— Innerhalb eines endlichen Bereichs wird die "hintere Hälfte" des Wertebereichs als negativ neu interpretiert.


Die einfachste Idee: Vorzeichen-Betrag (Originalcode)

Die direkteste Idee zur Darstellung negativer Zahlen: das höchste Bit alsVorzeichenbit verwenden。

  • Höchstes Bit 0 = positive Zahl
  • Höchstes Bit 1 = negative Zahl
  • Die restlichen Bits stellen den Absolutwert dar

Nehmen wir 8 Bits als Beispiel:

+5 =
0
0
0
0
0
1
0
1
Vorzeichenbit 0 = positiv
-5 =
1
0
0
0
0
1
0
1
Vorzeichenbit 1 = negativ

Klingt vernünftig, oder? Aber Vorzeichen-Betrag hat zwei fatale Probleme.

Problem eins: +0 und -0 existieren beide.

Positive 0 ist00000000, negative 0 ist10000000。

Der gleiche Wert hat zwei Darstellungen.

Das bedeutet, dass jede Vergleichsoperation (Prüfen, ob zwei Zahlen gleich sind) erfordert, dass die Hardware zusätzlich diesen Sonderfall behandelt.

Problem 2: Der Addierer kann nicht subtrahieren

Berechnung mit Vorzeichen-Betrag5 + (-3):

5 (Vorzeichen-Betrag)
00000101
+
-3 (Vorzeichen-Betrag)
10000011
=
Direkt addieren
10001000
=
Ergebnis
-8

5 + (-3) sollte 2 ergeben, aber direkte Addition mit Vorzeichen-Betrag ergibt -8.

Das bedeutet, dass die CPU separat eine Subtraktionsschaltung entwerfen muss, anstatt die vorhandene Additionsschaltung wiederzuverwenden.

Das Problem mit der Vorzeichen-Betrag-Darstellung lässt sich so zusammenfassen: intuitiv, aber nicht praktikabel. Sie eignet sich für Menschen zum Lesen, aber nicht für die Schaltungsimplementierung.


Die Offenbarung der Uhr: Modulare Arithmetik

Bevor wir fortfahren, müssen wir ein Schlüsselkonzept verstehen –Modulo-Operation。

Betrachten Sie eine Uhr: Das Zifferblatt hat nur 12 Markierungen.

Ausgehend von 10 Uhr möchten Sie auf 7 Uhr einstellen. Es gibt zwei Möglichkeiten:

Methode eins (gegen den Uhrzeigersinn): 10 - 3 =7(Subtraktion)

Methode 2 (im Uhrzeigersinn): 10 + 9 = 19, 19 mod 12 =7(Addition)

In einem „Modulo-12“-System sind -3 und +9 äquivalent (weil -3 + 12 = 9).

Allgemeiner:In einem Modulo-M-System ist das Subtrahieren einer Zahl äquivalent zum Addieren von (M - diese Zahl)。

Das Zweierkomplement wendet diese Idee auf Binärzahlen an. Für 8-Bit-Binärzahlen ist der Modul 2^8 = 256.

Das Zweierkomplement von -3 = 256 - 3 = 253 = binär.11111101。

therefore5 + (-3)Kann werden5 + 253 = 258。

Aber in einem 8-Bit-System überschreitet 258 die Obergrenze von 255. Der Übertrag vom höchsten Bit „läuft über“ und wird verworfen, sodass258 mod 256 = 2。

Die Addition ergab das korrekte Ergebnis der Subtraktion.


Zweierkomplement-Ringdiagramm (interaktive Demo)

Das Ringdiagramm unten zeigt intuitiv ein 4-Bit-Zweierkomplement-System.

4 Bits können 16 Werte darstellen (0~15); das Zweierkomplement interpretiert sie als -8~+7.

Ziehe den Schieberegler, um den Wert zu ändern, und beobachte die Position des Zeigers auf dem Ring.

Zweierkomplement-Ringdiagramm (4-Bit-System, Bereich -8 ~ +7)

0

Binärdarstellung:0000| Vorzeichenlose Interpretation:0

Lade die Plotly.js-Bibliothek, um ein polares Donutdiagramm zu zeichnen

Plotly-Zweierkomplement-Ringdiagramm (scatterpolar-Polarkoordinaten).

Die folgende Abbildung verwendet Plotly.js-Polarkoordinaten, um die 16 Teilstriche des 4-Bit-Zweierkomplements anzuzeigen. Blau = positiver Bereich, Rot = negativer Bereich, Grün = Nullpunkt.

Positiver Bereich (0 bis +7)
Negativer Zahlenbereich (-8 bis -1)
Nullpunkt
Überlaufpunkt (+7→-8)

Bedeutung von zyklisch:

  • Richtung im Uhrzeigersinn = Wert nimmt zu
  • Einmal im Kreis gehen (Überschreiten des Maximums) = Rückkehr zum Ausgangspunkt (der „Überlauf“-Effekt der modularen Arithmetik)
  • Roter Bereich = negative Zahlen (Zweierkomplement-Interpretation), blauer Bereich = positive Zahlen
  • Der innere Ring ist mit der vorzeichenlosen Interpretation beschriftet (entspricht 0~15), der äußere Ring ist mit der Zweierkomplement-Interpretation beschriftet (-8~+7)

Aus dem Diagramm ist ersichtlich, dassdas Zweierkomplement den Wertebereich im Wesentlichen zu einem Ring faltet.。

Wenn positive Zahlen das Ende erreichen (nach +7), werden sie natürlicherweise negativ (-8). Subtraktion und Addition verlaufen auf dem Ring in derselben Richtung.


Berechnungsregeln für das Zweierkomplement

Um das Zweierkomplement einer Zahl manuell zu berechnen, gibt es zwei äquivalente Schnellmethoden:

Methode eins: invertieren und 1 addieren (Standardmethode)

  1. Schreibe die dem Absolutwert entsprechende Binärdarstellung
  2. Invertiere jedes Bit (0 wird 1, 1 wird 0), um das Einerkomplement zu erhalten
  3. Addiere 1 zum Einerkomplement, um das Zweierkomplement zu erhalten

Methode zwei: von rechts nach links (Schnellberechnungsmethode)

  1. Finde die erste 1 von rechts nach links
  2. Invertiere alle Bits links von dieser 1
  3. Diese 1 und alle Bits rechts davon bleiben unverändert

Nimm -20 als Beispiel (8-Bit-System) und überprüfe mit beiden Methoden:

20 in binärer Darstellung
00010100
→
Invertieren (Einerkomplement)
11101011
→
1 addieren (Zweierkomplement)
11101100

Überprüfung: Das Zweierkomplement von -20 ist 11101100; als vorzeichenlos interpretiert, ergibt es 236.

236 + 20 = 256, genau ein Modulus. Das zeigt, dass die Berechnung korrekt ist.


Vergleich der drei Darstellungen

valueVorzeichen-Betrag-DarstellungEinerkomplementZweierkomplement
+5000001010000010100000101
-5100001011111101011111011
+0000000000000000000000000
-01000000011111111Existiert nicht
+127011111110111111101111111
-127111111111000000010000001
-128Kann nicht dargestellt werdenKann nicht dargestellt werden10000000

Beachten Sie drei wichtige Unterschiede:

  1. Zweierkomplement hat keine -0: Alle Einsen werden nicht mehr für -0 verschwendet, sondern stellen -1 dar.
  2. Zweierkomplement hat eine zusätzliche negative ZahlDer Wertebereich des 8-Bit-Zweierkomplements ist -128 ~ +127, was ein -128 mehr enthält als der Wertebereich der Vorzeichen-Betrag-Darstellung von -127 ~ +127.
  3. Zweierkomplement vereinheitlicht die Addition: Die Addition positiver und negativer Zahlen verwendet dieselbe Schaltung.

Deshalb ist JavasbyteDer Typbereich ist -128~127, und der von Csigned charist ebenfalls -128~127. Beide verwenden das 8-Bit-Zweierkomplement.


Interaktive Demo: Zweierkomplement-Rechner

Geben Sie unten eine Ganzzahl im 8-Bit-Bereich (-128 ~ 127) ein, um ihre Vorzeichen-Betrag-Darstellung, ihr Einerkomplement und ihr Zweierkomplement anzuzeigen.

Zweierkomplement-Rechner (8-Bit)

Ganzzahl eingeben:

Besonderheit: Wenn -128 eingegeben wird, lautet das Zweierkomplement10000000。

Der Absolutbetrag dieser Zahl ist 128, aber 128 liegt außerhalb des positiven 8-Bit-Bereichs (0~127).

Daher hat -128 keine entsprechende positive Darstellung—dies ist eine interessante Eigenschaft des Zweierkomplementsystems.


Zweierkomplement-Überlauf

Der Bereich des 8-Bit-Zweierkomplements ist -128~127.

Wenn das Berechnungsergebnis diesen Bereich überschreitet, wird es einenÜberlauf verursachen。

127 + 1
-128
(Positiver Überlauf, negatives Ergebnis)
|
-128 - 1
+127
(Negativer Überlauf, positives Ergebnis)

Dies erinnert wieder an das frühere Ringdiagramm – nach Erreichen des Endes „läuft der Wert um“.

In C/C++ ist der Überlauf von vorzeichenbehafteten Ganzzahlenundefiniertes Verhalten(der Compiler darf annehmen, dass er nie auftritt), was die Ursache vieler seltsamer Fehler ist.


Code-Demo: vollständige Implementierung des Zweierkomplements

Beispiel

# Vollständige Implementierung und Verifikation des Zweierkomplements (Beispiel-Demo)

def to_twos_complement(n, bits=8):
    Konvertiere die Ganzzahl n in eine Zweierkomplement-Zeichenkette mit der angegebenen Bitbreite
    if n < -2**(bits-1) or n > 2**(bits-1) - 1:
        raise ValueError(fWert {n} liegt außerhalb des {bits}-Bit-Zweierkomplement-Bereichs [-{2**(bits-1)}, {2**(bits-1)-1}])

    # Modulo-Arithmetik verwenden: das vorzeichenlose Äquivalent einer negativen Zahl n = n + 2^bits
    # Bitmaske direkt in Python verwenden
    mask = (1 << bits) - 1  # Zum Beispiel 8 Bits: 0xFF = 255
    unsigned_val = n & mask
    return format(unsigned_val, f'0{bits}b')

def from_twos_complement(bin_str):
    """Konvertiert einen Zweierkomplement-String zurück in eine dezimale Ganzzahl."""
    bits = len(bin_str)
    unsigned_val = int(bin_str, 2)

    # Wenn das höchstwertige Bit 1 ist (negativ), subtrahiere 2^bits
    if bin_str[0] == '1':
        return unsigned_val - (1 << bits)
    else:
        return unsigned_val

def show_conversion_steps(n, bits=8):
    """Zeige die detaillierten Schritte von n zum Zweierkomplement."""
    abs_bin = format(abs(n), f'0{bits}b')
    print⟦⟧"Wert: {n}"(f"Value: {n}")
    print(fSchritt 1: Absolutwert |{n}| = {abs(n)} → Binärdarstellung = {abs_bin})

    if n >= 0:
        print(f" Schritt 2: Das Zweierkomplement einer positiven Zahl = Vorzeichen-Betrag-Darstellung = {abs_bin}")
    else:
        # Invertieren
        inverted = ''.join('1' if b == '0' else '0' for b in abs_bin)
        print(fSchritt 2: Invertieren (Einerkomplement) = {inverted})

        # 1 addieren
        carry = 1
        result_list = list(inverted)
        for i in range(bits - 1, -1, -1):
            if carry == 0:
                break
            total = int(result_list[i]) + carry
            result_list[i] = str(total % 2)
            carry = total // 2
        result = ''.join(result_list)
        print(fSchritt 3: Einerkomplement + 1 = {result})

    comp = to_twos_complement(n, bits)
    print(f" → Endgültiges Zweierkomplement: {comp}")

    # Überprüfen
    recovered = from_twos_complement(comp)
    print(fÜberprüfung: Zweierkomplement {comp} → interpretiert als {recovered})
    print()

# Test
print("=" * 50)
print("Demo zur Zweierkomplement-Konvertierung (8-Bit-System)")
print("=" * 50)
print()

for val in [5, -5, 0, -1, 127, -128]:
    show_conversion_steps(val)

# Zweierkomplement-Addition überprüfen
print("=" * 50)
print("Überprüfung der Zweierkomplement-Addition: Subtraktion wird zur Addition")
print("=" * 50)

def twos_add(a, b, bits=8):
    """Zweierkomplement-Addition (einschließlich Überlaufbehandlung)"""
    mask = (1 << bits) - 1
    result_unsigned = (a + b) & mask
    # Vorzeichenbehaftetes Ergebnis interpretieren
    if result_unsigned >= (1 << (bits - 1)):
        signed_result = result_unsigned - (1 << bits)
    else:
        signed_result = result_unsigned
    return signed_result

# 5 - 3 = 2
print(f5 + (-3) = {twos_add(5, -3)} (erwartet: 2))

# 127 + 1 = -128 (Überlauf)
print(f127 + 1 = {twos_add(127, 1)} (erwartet: -128, Überlauf aufgetreten))

# -128 - 1 = 127 (Überlauf)
print(f-128 + (-1) = {twos_add(-128, -1)} (erwartet: 127, Überlauf aufgetreten))

# Null addieren
print(f0 + 0 = {twos_add(0, 0)} (erwartet: 0))

# Mit EXAMPLE-Zeichen überprüfen: R=82, U=85 → 82 + (-85) = -3
print(f"\nEXAMPLE-Test:R(82) + (-U(85)) = {twos_add(82, -85)} (erwartet: -3)")

# Veranschaulichung der zyklischen Natur des Zweierkomplements
print("\nZweierkomplement-Ring (vereinfachte 4-Bit-Version, Bereich -8 bis +7):)
print("Wert | Zweierkomplement | Vorzeichenlos")
print("-" * 30)
for n in range(-8, 8):
    comp = to_twos_complement(n, 4)
    unsig = int(comp, 2)
    print(f" {n:3d} | {comp} | {unsig:2d}")
Weitere Erweiterungen