x86-Assembly, 9 Byte (für konkurrierende Einträge)
Jedem, der diese Herausforderung in Hochsprachen versucht, entgeht der wahre Spaß, rohe Teile zu manipulieren. Es gibt so viele subtile Variationen, es ist verrückt - und es macht viel Spaß, darüber nachzudenken. Hier sind einige Lösungen, die ich in der 32-Bit-x86-Assemblersprache entwickelt habe.
Ich entschuldige mich im Voraus, dass dies nicht die typische Code-Golf-Antwort ist. Ich werde viel über den Gedankenprozess der iterativen Optimierung (für die Größe) streifen. Hoffentlich ist das interessant und lehrreich für ein größeres Publikum, aber wenn Sie der TL; DR-Typ sind, werde ich nicht beleidigt sein, wenn Sie bis zum Ende springen.
Die naheliegende und effiziente Lösung besteht darin, zu testen, ob der Wert ungerade oder gerade ist (was effizient durch Betrachten des niedrigstwertigen Bits erfolgen kann), und dann entsprechend zwischen n + 1 oder n - 1 zu wählen . Unter der Annahme, dass die Eingabe als Parameter im ECX
Register übergeben wird und das Ergebnis im EAX
Register zurückgegeben wird, erhalten wir die folgende Funktion:
F6 C1 01 | test cl, 1 ; test last bit to see if odd or even
8D 41 01 | lea eax, DWORD PTR [ecx + 1] ; set EAX to n+1 (without clobbering flags)
8D 49 FF | lea ecx, DWORD PTR [ecx - 1] ; set ECX to n-1 (without clobbering flags)
0F 44 C1 | cmovz eax, ecx ; move in different result if input was even
C3 | ret
(13 Bytes)
Für Code-Golf-Zwecke sind diese LEA
Anweisungen jedoch nicht besonders gut, da sie 3 Byte zum Codieren benötigen. Eine einfache DEC
Rementation von ECX
wäre viel kürzer (nur ein Byte), dies wirkt sich jedoch auf Flags aus. Wir müssen also etwas geschickter sein, wie wir den Code anordnen. Wir können die Abnahme tun zuerst , und die gerade / ungerade Test zweite , aber dann haben wir das Ergebnis der gerade / ungerade Test invertieren.
Außerdem können wir die Anweisung zum bedingten Verschieben in eine Verzweigung ändern, wodurch der Code möglicherweise langsamer ausgeführt wird (je nachdem, wie vorhersehbar die Verzweigung ist) Muster, es wird schneller sein), was uns ein weiteres Byte sparen wird.
Tatsächlich kann mit dieser Überarbeitung die gesamte Operation an Ort und Stelle ausgeführt werden, wobei nur ein einziges Register verwendet wird. Das ist großartig, wenn Sie diesen Code irgendwo einfügen (und es ist wahrscheinlich, dass er so kurz ist).
48 | dec eax ; decrement first
A8 01 | test al, 1 ; test last bit to see if odd or even
75 02 | jnz InputWasEven ; (decrement means test result is inverted)
40 | inc eax ; undo the decrement...
40 | inc eax ; ...and add 1
InputWasEven: ; (two 1-byte INCs are shorter than one 3-byte ADD with 2)
(Inline: 7 Bytes; als Funktion: 10 Bytes)
Aber was, wenn Sie es zu einer Funktion machen wollten? Keine Standardaufrufkonvention verwendet für die Übergabe von Parametern dasselbe Register wie für den Rückgabewert. Daher müssen Sie MOV
am Anfang oder Ende der Funktion eine Register-Register- Anweisung hinzufügen . Dies hat praktisch keine Kosten für die Geschwindigkeit, fügt jedoch 2 Bytes hinzu. (Die RET
Anweisung fügt auch ein Byte hinzu, und es entsteht ein gewisser Overhead durch die Notwendigkeit, einen Funktionsaufruf auszuführen und von diesem zurückzukehren. Dies ist also ein Beispiel, bei dem Inlining sowohl einen Geschwindigkeits- als auch einen Größenvorteil erzeugt, anstatt nur eine klassische Geschwindigkeit zu sein -für-den-Raum-Kompromiss.) Insgesamt wird dieser Code als Funktion auf 10 Bytes aufgebläht.
Was können wir in 10 Bytes noch tun? Wenn es uns überhaupt um Leistung geht (zumindest vorhersehbare Leistung), wäre es schön, diesen Zweig loszuwerden. Hier ist eine verzweigungslose Bit-Twiddling-Lösung mit der gleichen Größe in Bytes. Die Grundvoraussetzung ist einfach: Wir verwenden ein bitweises XOR, um das letzte Bit umzukehren und einen ungeraden Wert in einen geraden Wert umzuwandeln und umgekehrt. Aber es gibt ein Problem - für ungerade Eingaben erhalten wir n-1 , für gerade Eingaben erhalten wir n + 1 - genau das Gegenteil von dem, was wir wollen. Um dies zu beheben, führen wir die Operation mit einem negativen Wert durch, wobei das Vorzeichen effektiv umgedreht wird.
8B C1 | mov eax, ecx ; copy parameter (ECX) to return register (EAX)
|
F7 D8 | neg eax ; two's-complement negation
83 F0 01 | xor eax, 1 ; XOR last bit to invert odd/even
F7 D8 | neg eax ; two's-complement negation
|
C3 | ret ; return from function
(Inline: 7 Bytes; als Funktion: 10 Bytes)
Ziemlich glatt; Es ist schwer zu erkennen, wie das verbessert werden kann. Eines fällt mir jedoch auf: diese zwei 2-Byte- NEG
Anweisungen. Ehrlich gesagt scheinen zwei Bytes ein Byte zu viel zu sein, um eine einfache Negation zu codieren, aber das ist der Befehlssatz, mit dem wir arbeiten müssen. Gibt es Workarounds? Sicher! Wenn wir XOR
durch -2 ersetzen, können wir die zweite NEG
Ation durch eine INC
Zement ersetzen:
8B C1 | mov eax, ecx
|
F7 D8 | neg eax
83 F0 FE | xor eax, -2
40 | inc eax
|
C3 | ret
(Inline: 6 Bytes; als Funktion: 9 Bytes)
Ein weiterer die Merkwürdigkeiten des x86 - Befehlssatzes ist die Mehrzweck LEA
Anweisung , die ein Register-Register bewegen tun, ein Register-Register zusätzlich durch eine konstanten Ausgleich, und das alles in einem einzigen Befehl Skalierung!
8B C1 | mov eax, ecx
83 E0 01 | and eax, 1 ; set EAX to 1 if even, or 0 if odd
8D 44 41 FF | lea eax, DWORD PTR [ecx + eax*2 - 1]
C3 | ret
(10 Bytes)
Der AND
Befehl entspricht dem TEST
zuvor verwendeten Befehl, da beide ein bitweises UND ausführen und die Flags entsprechend setzen, aber AND
den Zieloperanden tatsächlich aktualisieren. Der LEA
Befehl skaliert dies dann um 2, addiert den ursprünglichen Eingabewert und dekrementiert ihn um 1. Wenn der Eingabewert ungerade war, subtrahiert dies 1 (2 × 0 - 1 = –1) davon; Wenn der Eingabewert gerade war, wird 1 (2 × 1 - 1 = 1) hinzugefügt.
Dies ist eine sehr schnelle und effiziente Methode, um den Code zu schreiben, da ein Großteil der Ausführung im Front-End ausgeführt werden kann, aber es bringt uns nicht viel mit Bytes, da so viele zum Codieren eines Komplexes erforderlich sind LEA
Anweisung. Diese Version funktioniert auch nicht so gut für Inlining-Zwecke, da der ursprüngliche Eingabewert als Eingabe der LEA
Anweisung beibehalten werden muss . Mit diesem letzten Optimierungsversuch sind wir also tatsächlich zurückgegangen, was darauf hindeutet, dass es an der Zeit ist, damit aufzuhören.
Für den letzten konkurrierenden Eintrag haben wir also eine 9-Byte-Funktion, die den Eingabewert in das ECX
Register (eine registerbasierte Semistandard-Aufrufkonvention für 32-Bit x86) aufnimmt und das Ergebnis im EAX
Register zurückgibt (wie bei alle x86-Aufrufkonventionen):
SwapParity PROC
8B C1 mov eax, ecx
F7 D8 neg eax
83 F0 FE xor eax, -2
40 inc eax
C3 ret
SwapParity ENDP
Montagefertig mit MASM; Aufruf von C als:
extern int __fastcall SwapParity(int value); // MSVC
extern int __attribute__((fastcall)) SwapParity(int value); // GNU