====== std.conv — Zahlensysteme und Bitoperationen ====== → [[lyx_-_programmiersprache:units|Zurück zur Unit-Übersicht]] Zwei Themen: **Zahlen zwischen Textform und Wert wandeln** (hexadezimal, binär, oktal, mit Tausendertrennzeichen) und **auf einzelnen Bits arbeiten** — Bits setzen und lesen, Bitfelder ausschneiden, Nullbits zählen, Byte-Reihenfolge tauschen, Vorzeichen erweitern, Adressen ausrichten. Alles rechnet auf ''int64''. Die Unit ist der Werkzeugkasten für hardwarenahe Arbeit: Protokollfelder, Register, Binärformate, Fehlersuche. **Autor:** Andreas Röne\\ **Copyright:** 2024–2025 Andreas Röne\\ **Quelle:** ''std/conv.lyx'' ''RotateLeft32'' maskiert nicht auf 32 Bit — ''0x80000000 <<< 1'' ergibt 4294967297 statt 1 (#1495). ''RotateRight32'' arbeitet korrekt. ''ParseHex'', ''ParseBin'' und ''ParseOct'' melden **ungültige Eingaben nicht** und behandeln Störzeichen uneinheitlich (#1500). Details unter [[#Fallstricke]]. ===== Import ===== import std.conv; ---- ===== Die Operatoren der Sprache ===== Für die Grundrechenarten auf Bits braucht es keine Unit — Lyx hat sie eingebaut: ^ Operator ^ Bedeutung ^ Beispiel ^ | ''&'' | UND | ''12 & 10'' = 8 | | ''%%|%%'' | ODER | ''12 %%|%% 10'' = 14 | | ''^'' | XOR | ''12 ^ 10'' = 6 | | ''~'' | Komplement | ''~12'' = −13 | | ''<<'' | Linksschieben | ''1 << 10'' = 1024 | | ''%%>>%%'' | Rechtsschieben | ''1024 %%>>%% 3'' = 128 | | ''%%|~%%'' | NOR | ''12 %%|~%% 10'' = −15 | Zwei Eigenheiten, die man kennen muss: **''%%>>%%'' schiebt arithmetisch.** Das Vorzeichenbit wird nachgezogen: ''−16 %%>>%% 2'' ergibt −4, ''−1 %%>>%% 1'' bleibt −1. Für ein logisches Rechtsschieben — bei dem Nullen nachrücken — muss der Wert vorher umgedeutet werden: var logisch: int64 := (wert as uint64) >> n; Genau so macht es diese Unit intern in ''SwapEndian*'' und ''RotateRight32''. **Schiebeweiten werden modulo 64 genommen.** ''1 << 64'' ergibt 1, ''1 << 65'' ergibt 2, ''1 << −1'' ergibt das Vorzeichenbit. Das entspricht dem Verhalten der Prozessorbefehle, wird aber leicht übersehen, wenn die Schiebeweite aus einer Rechnung stammt. Wer sie nicht selbst in der Hand hat, prüft sie vorher gegen 0…63. ---- ===== Zahlensysteme ===== ==== Ausgeben ==== ^ Signatur ^ Beschreibung ^ | ''IntToHex(val, digits: int64): pchar'' | Hexadezimal mit fester Stellenzahl, Großbuchstaben | | ''IntToHex8'' · ''IntToHex16'' · ''IntToHex32'' · ''IntToHex64'' | 2, 4, 8 bzw. 16 Stellen | | ''IntToBin(val, bits: int64): pchar'' | Binär mit fester Stellenzahl | | ''IntToBin8'' · ''IntToBin16'' · ''IntToBin32'' · ''IntToBin64'' | 8, 16, 32 bzw. 64 Stellen | | ''IntToOct(val, digits: int64): pchar'' | Oktal mit fester Stellenzahl | Negative Werte erscheinen im Zweierkomplement: ''IntToHex64(−1)'' ergibt ''FFFFFFFFFFFFFFFF'', ''IntToBin8(−1)'' ergibt ''11111111''. Reicht die Stellenzahl nicht, werden die **höherwertigen Stellen abgeschnitten** — ''IntToHex(255, 1)'' ergibt ''F''. Es gibt keine Meldung, die Zahl passt also stillschweigend nicht mehr. ==== Einlesen ==== ^ Signatur ^ Beschreibung ^ Status ^ | ''ParseHex(s: pchar): int64'' | Hexadezimal; erkennt ''0x''-Präfix, Groß- und Kleinschreibung | Fehler nicht meldbar (#1500) | | ''ParseBin(s: pchar): int64'' | Binär; erkennt ''0b''-Präfix | dito | | ''ParseOct(s: pchar): int64'' | Oktal; führende ''0'' zulässig | dito | Der Rundlauf funktioniert: ''ParseHex(IntToHex32(x))'' und ''ParseBin(IntToBin16(x))'' liefern den Ausgangswert zurück. ==== Lesbare Zahlen ==== ^ Signatur ^ Beschreibung ^ | ''IntToStrWithSeparator(n, sep: int64): pchar'' | Dreiergruppen mit frei wählbarem Trennzeichen (Zeichencode) | | ''IntToStrWithComma(n: int64): pchar'' | ''1,234,567'' | | ''IntToStrWithUnderscore(n: int64): pchar'' | ''1_234_567'' | Für die deutsche Schreibweise ''IntToStrWithSeparator(n, 46)'' — 46 ist der Punkt. Negative Zahlen und Werte unter 1000 werden korrekt behandelt. ---- ===== Bitoperationen ===== ==== Einzelne Bits ==== ^ Signatur ^ Beschreibung ^ | ''GetBit(val, bit_pos: int64): bool'' | Ist das Bit gesetzt? | | ''SetBit(val, bit_pos: int64): int64'' | Bit setzen | | ''ClearBit(val, bit_pos: int64): int64'' | Bit löschen | | ''ToggleBit(val, bit_pos: int64): int64'' | Bit umschalten | Alle vier prüfen die Position gegen 0…63. Außerhalb liefert ''GetBit'' ''false'', die übrigen geben den Wert **unverändert** zurück — ein Tippfehler bei der Position bleibt damit folgenlos, aber auch unbemerkt. ==== Bitfelder ==== ^ Signatur ^ Beschreibung ^ | ''ExtractBits(val, start, count: int64): int64'' | ''count'' Bits ab Position ''start'' herausziehen | | ''InsertBits(val, bits, start, count: int64): int64'' | ''count'' Bits ab ''start'' ersetzen | Beide prüfen, dass ''start + count'' innerhalb von 64 Bit bleibt; sonst kommt 0 bzw. der unveränderte Wert zurück. ==== Zählen ==== ^ Signatur ^ Beschreibung ^ | ''CountLeadingZeros(val: int64): int64'' | Führende Nullbits; für 0 sind es 64 | | ''CountTrailingZeros(val: int64): int64'' | Nullbits am unteren Ende; für 0 sind es 64 | Ein ''PopCount'' — die Zahl der gesetzten Bits — gibt es hier nicht, sondern in [[lyx_-_programmiersprache:units:math|std.math]]; dort ist es allerdings für negative Werte fehlerhaft (#1448). ==== Bytes und Byte-Reihenfolge ==== ^ Signatur ^ Beschreibung ^ | ''SwapEndian16(val: int64): int64'' | Zwei Bytes tauschen | | ''SwapEndian32(val: int64): int64'' | Vier Bytes umdrehen | | ''SwapEndian64(val: int64): int64'' | Acht Bytes umdrehen | | ''GetByte(val, byte_pos: int64): int64'' | Einzelnes Byte, Position 0 ist das niederwertigste | | ''SetByte(val, byte_pos, byte_val: int64): int64'' | Einzelnes Byte ersetzen | ==== Rotation ==== ^ Signatur ^ Beschreibung ^ Status ^ | ''RotateLeft32(val, n: int64): int64'' | Linksrotation über 32 Bit | **überschreitet 32 Bit** (#1495) | | ''RotateRight32(val, n: int64): int64'' | Rechtsrotation über 32 Bit | ok | 64-Bit-Rotationen gibt es nicht. ==== Breiten und Ausrichtung ==== ^ Signatur ^ Beschreibung ^ | ''SignExtend8'' · ''SignExtend16'' · ''SignExtend32'' | Kleinere Breite vorzeichenrichtig auf 64 Bit bringen: ''SignExtend8(255)'' = −1 | | ''ZeroExtend8'' · ''ZeroExtend16'' · ''ZeroExtend32'' | Nur die unteren Bits behalten: ''ZeroExtend8(−1)'' = 255 | | ''ClampUint8'' · ''ClampUint16'' · ''ClampUint32'' | Auf den vorzeichenlosen Bereich begrenzen statt abzuschneiden | | ''AlignUp(addr, alignment: int64): int64'' | Nächstes Vielfaches nach oben | | ''AlignDown(addr, alignment: int64): int64'' | Nächstes Vielfaches nach unten | | ''IsAligned(addr, alignment: int64): bool'' | Bereits ausgerichtet? | Der Unterschied zwischen ''ZeroExtend8'' und ''ClampUint8'' ist wesentlich: Ersteres **schneidet** ab (300 wird zu 44), Letzteres **begrenzt** (300 wird zu 255). ---- ===== Beispiel ===== import std.conv; fn main(): int64 { PrintLn("-- Zahlensysteme:"); PrintLn("IntToHex8(255) = ", IntToHex8(255)); PrintLn("IntToHex32(305419896) = ", IntToHex32(305419896)); PrintLn("IntToBin8(5) = ", IntToBin8(5)); PrintLn("IntToOct(511, 3) = ", IntToOct(511, 3)); PrintLn("IntToHex64(-1) = ", IntToHex64(-1), " (Zweierkomplement)"); PrintLn("-- und zurueck:"); PrintLn("ParseHex('DEADBEEF') = ", IntToStr(ParseHex("DEADBEEF"))); PrintLn("ParseHex('0xFF') = ", IntToStr(ParseHex("0xFF"))); PrintLn("ParseBin('0b1010') = ", IntToStr(ParseBin("0b1010"))); PrintLn("ParseOct('0777') = ", IntToStr(ParseOct("0777"))); PrintLn("-- Einzelne Bits:"); var flags: int64 := 0; flags := SetBit(flags, 0); flags := SetBit(flags, 3); PrintLn("nach SetBit(0) und SetBit(3): ", IntToBin8(flags), " = ", IntToStr(flags)); PrintLn("GetBit(3) = ", IntToStr(GetBit(flags, 3) as int64)); flags := ClearBit(flags, 0); PrintLn("nach ClearBit(0): ", IntToBin8(flags)); flags := ToggleBit(flags, 7); PrintLn("nach ToggleBit(7): ", IntToBin8(flags)); PrintLn("-- Bitfelder lesen und schreiben:"); var wort: int64 := 0; wort := InsertBits(wort, 5, 4, 4); // 4 Bits ab Position 4 wort := InsertBits(wort, 3, 0, 4); // 4 Bits ab Position 0 PrintLn("zusammengesetzt: ", IntToBin8(wort), " = ", IntToStr(wort)); PrintLn("oberes Nibble = ", IntToStr(ExtractBits(wort, 4, 4))); PrintLn("unteres Nibble = ", IntToStr(ExtractBits(wort, 0, 4))); PrintLn("-- Bits zaehlen:"); PrintLn("CountLeadingZeros(255) = ", IntToStr(CountLeadingZeros(255))); PrintLn("CountTrailingZeros(8) = ", IntToStr(CountTrailingZeros(8))); PrintLn("-- Byte-Reihenfolge und Bytes:"); PrintLn("SwapEndian32(0x12345678) = ", IntToHex32(SwapEndian32(305419896))); PrintLn("GetByte(0x12345678, 0) = ", IntToHex8(GetByte(305419896, 0))); PrintLn("GetByte(0x12345678, 3) = ", IntToHex8(GetByte(305419896, 3))); PrintLn("-- Breiten und Ausrichtung:"); PrintLn("SignExtend8(255) = ", IntToStr(SignExtend8(255))); PrintLn("ZeroExtend8(-1) = ", IntToStr(ZeroExtend8(-1))); PrintLn("ClampUint8(300) = ", IntToStr(ClampUint8(300))); PrintLn("AlignUp(100, 16) = ", IntToStr(AlignUp(100, 16))); PrintLn("IsAligned(96, 16) = ", IntToStr(IsAligned(96, 16) as int64)); PrintLn("-- Lesbare Zahlen:"); PrintLn("IntToStrWithComma(1234567) = ", IntToStrWithComma(1234567)); PrintLn("IntToStrWithSeparator(1234567, 46) = ", IntToStrWithSeparator(1234567, 46)); return 0; } Ausgabe: -- Zahlensysteme: IntToHex8(255) = FF IntToHex32(305419896) = 12345678 IntToBin8(5) = 00000101 IntToOct(511, 3) = 777 IntToHex64(-1) = FFFFFFFFFFFFFFFF (Zweierkomplement) -- und zurueck: ParseHex('DEADBEEF') = 3735928559 ParseHex('0xFF') = 255 ParseBin('0b1010') = 10 ParseOct('0777') = 511 -- Einzelne Bits: nach SetBit(0) und SetBit(3): 00001001 = 9 GetBit(3) = 1 nach ClearBit(0): 00001000 nach ToggleBit(7): 10001000 -- Bitfelder lesen und schreiben: zusammengesetzt: 01010011 = 83 oberes Nibble = 5 unteres Nibble = 3 -- Bits zaehlen: CountLeadingZeros(255) = 56 CountTrailingZeros(8) = 3 -- Byte-Reihenfolge und Bytes: SwapEndian32(0x12345678) = 78563412 GetByte(0x12345678, 0) = 78 GetByte(0x12345678, 3) = 12 -- Breiten und Ausrichtung: SignExtend8(255) = -1 ZeroExtend8(-1) = 255 ClampUint8(300) = 255 AlignUp(100, 16) = 112 IsAligned(96, 16) = 1 -- Lesbare Zahlen: IntToStrWithComma(1234567) = 1,234,567 IntToStrWithSeparator(1234567, 46) = 1.234.567 Das Zusammenspiel von ''InsertBits'' und ''IntToBin8'' ist beim Bauen von Protokollfeldern hilfreich: man sieht das Bitmuster unmittelbar. ---- ===== Fallstricke ===== Geprüft mit ''lyxc 1.0.21A''. ==== RotateLeft32 verlässt den 32-Bit-Bereich (#1495) ==== ^ Aufruf ^ geliefert ^ erwartet ^ | ''0x80000000 <<< 1'' | 4294967297 | 1 | | ''0xFFFFFFFF <<< 4'' | 68719476735 | 4294967295 | | ''0x12345678 <<< 8'' | 78187493394 | 878082066 | Die herausgeschobenen Bits fallen nicht weg, sondern bleiben im ''int64'' stehen. ''RotateRight32'' ist davon nicht betroffen. Das trifft insbesondere Hashverfahren wie MD5, SHA-1 oder ChaCha, deren Kern die 32-Bit-Linksrotation ist. Bis zum Fix selbst maskieren: var rotiert: int64 := RotateLeft32(wert, n) & 4294967295; ==== Die Parser melden keine Fehler (#1500) ==== ^ Eingabe ^ Ergebnis ^ Verhalten ^ | ''ParseHex("FFxy")'' | 255 | bricht bei ''x'' ab | | ''ParseHex("12 34")'' | 4660 | **überspringt** das Leerzeichen und liest ''1234'' | | ''ParseHex("-FF")'' | 255 | Minuszeichen wird ignoriert | | ''ParseBin("1010abc")'' | 10 | bricht ab | | ''ParseOct("7778")'' | 511 | bricht bei der ''8'' ab | | ''ParseHex("ZZ")'' | 0 | nichts gelesen | | ''ParseHex("0")'' | 0 | gültige Eingabe — nicht unterscheidbar | Zwei Dinge fallen zusammen: eine fehlgeschlagene Umwandlung ist am Rückgabewert nicht erkennbar, und Störzeichen führen mal zum Abbruch, mal werden sie übersprungen. Wer fremde Eingaben verarbeitet, prüft die Zeichen deshalb vorher selbst. ==== Zu wenige Stellen schneiden ab ==== ''IntToHex(255, 1)'' ergibt ''F'', ''IntToBin(255, 4)'' ergibt ''1111''. Die höherwertigen Stellen entfallen ohne Hinweis. Die festen Varianten (''IntToHex8'' und Verwandte) sind hier die sicherere Wahl, weil die Breite zum Wertebereich passt. ==== Positionsprüfungen schlucken Tippfehler ==== ''SetBit(wert, 99)'' gibt den Wert unverändert zurück, ''GetBit(wert, 64)'' liefert ''false''. Das verhindert Speicherfehler, verbirgt aber einen falschen Index — die Rückgabe sieht wie ein gültiges Ergebnis aus. ---- ===== Verwandte Units ===== * [[lyx_-_programmiersprache:units:math|std.math]] — ''PopCount'', ''IsPowerOfTwo'', ''NextPowerOfTwo'' * [[lyx_-_programmiersprache:units:buffer|std.buffer]] — Hex-Umwandlung ganzer Puffer statt einzelner Zahlen * [[lyx_-_programmiersprache:units:pack|std.pack]] — Zahlen mit fester Breite in Byte-Puffer schreiben * [[lyx_-_programmiersprache:sprache:syntax|Syntax]] — die Operatoren der Sprache im Überblick