Jedes Zeichen, Byte für Byte. UTF-8 ist die Regel, die aus Unicode-Zahlen Bytes macht — und der Vorrat ist endlich: 1 112 064 Zeichen, kein Stück mehr. Was endlich ist, lässt sich vollständig prüfen. Dieses Blatt codiert jeden einzelnen Wert von Hand, Bit für Bit, und hält das Ergebnis gegen den eingebauten Encoder des Browsers — beide Richtungen, keine Stichprobe.
- Vier Formen: 1, 2, 3 oder 4 Bytes — die führenden Bits sagen die Länge, das Präfix
- Übersetzer: Text eingeben, Zeichen für Zeichen die Bytes sehen, inklusive Bitmuster
- Fallen: Umlaute, Währungen, Astralzeichen, ZWJ-Familien — wo
.lengthlügt - Prüflauf: alle 1 112 064 Zeichen hin und zurück, feste Proben, Verwerfungen, zwei Gegenproben
Der Unicode-Skalarenraum ist geschlossen und endlich: 0 bis 0x10FFFF, abzüglich der
Surrogate 0xD800–0xDFFF, ergibt genau 1 112 064 Werte. Jeder davon wird hier einzeln durchprobiert
— gestichprobt wird nichts. Dazu kommt ein Präfixcode: Die führenden Bits eines Bytes kündigen
unmissverständlich an, wie viele Bytes dazugehören, und jedes Fortsetzungsbyte beginnt mit 10.
Deshalb ist der Code selbstverständlich zur vollständigen Prüfung geeignet, und die Zählung muss
mit der Theorie übereinstimmen.
| Behauptung | Ergebnis |
|---|---|
| alle Zeichen hin, Byte für Byte | 1 112 064 Zeichen von Hand codiert gegen den eingebauten Encoder · 0 Abweichungen |
| und wieder zurück | alle eigenen Bytefolgen zurückgedeutet · 0 daneben |
| die Längen liegen fest | 128 · 1 920 · 61 440 · 1 048 576 — gezählt und erwartet deckungsgleich |
| feste Proben aus der Dokumentation | 7 Proben ($ ¢ € ß ä 𝄞 𐍈) · 7 treffen Eigenbau und Browser zugleich |
| Verwerfungen, wo nichts zu holen ist | 10 kaputte Folgen · 10 von beiden Deutern zurückgewiesen |
| Gegenprobe: stille Ersetzung ertappt | der nachgiebige Dekoder: 10 von 10 still durchgelassen — ertappt |
| Gegenprobe: der Eine-Bahn-Codierer | 12 000 Proben · 12 000 erwischt |
| echte Sätze, zufällig gesät | 300 Sätze, bis zu 40 Zeichen · hin und zurück · 0 daneben |
Der Prüflauf läuft in 4,9 Sekunden — der gesamte Vorrat, beide Richtungen.
Der Überlang-Index war um eins verschoben. Im Deuter liegt die Untergrenze pro Bahnlänge in
einem Array: [0, 0x80, 0x800, 0x10000]. Ich hatte [...][bahnen] geschrieben — für bahnen=2
greift Index 2, also 0x800 statt 0x80. Ergebnis: alle 2-Byte- und 3-Byte-Werte wurden als
„überlang" verworfen. 63 360 Fehler, exakt die Summe aus 1 920 und 61 440 — die Prüfung hat es
sofort gemeldet, und die Zahl war so deutlich, dass die Ursache sofort klar war.
Der hexadezimale Index-Leck. Die Prüfungen vergleichen Strings wie „E2 82 AC" — selbst
geschrieben, Byte für Byte. Für die Zählung habe ich gruppe(punkt).map(hex) verwendet.
map() übermittelt aber den Array-Index als zweites Argument an die Callback-Funktion, und mein
hex-Helper akzeptiert ein zweites Argument für die Breite: padStart(st || 2, "0"). Für das
vierte Byte einer Vierergruppe (Index 3) wurde padStart(3, "0") aufgerufen — „9E" wurde zu
„09E". Zwei von sieben Proben schlugen fehl, und die Differenz war auf den ersten Blick unsichtbar:
ein führendes Nullbyte mehr. Der Fix war, .map(hex) durch .map((b) => hex(b)) zu ersetzen —
die Stellschraube war nicht die Logik, sondern eine unbeabsichtigte Seitenwirkung von .map().
Was der Browser hier ausmacht. Verglichen wird nicht mit sich selbst: TextEncoder und
TextDecoder sind die fertigen Implementierungen des Browsers — ein zweites Werkzeug mit anderer
Herkunft. Dazu kommen feste Proben aus der Unicode-Dokumentation mit hingeschriebenen Bytes. Wenn
eigene Bits, Browser und schriftliche Quelle übereinstimmen, sprechen zwei unabhängige Zeugen —
und die Gegenproben zeigen zusätzlich, dass der Vergleich überhaupt anschlagen kann.
Eine einzelne HTML-Datei. Kein Build, keine Bibliothek, nichts verlässt den Browser.
Codierung per Bit-Schieberei, Deutung als State-Machine mit strenger Fehlerbehandlung,
Prüflauf als asynchroner Block mit Fortschrittsanzeige. Hell und dunkel über
prefers-color-scheme und [data-theme].
Alle Blätter nach Feld geordnet, jedes mit eigenem Repo: ssims437.github.io
MIT