x86-64-Maschinencode, 30 Byte
31 C0 99 8B 4C B7 FC F6 C1 01 74 04 01 CA EB 02 01 C8 FF CE 75 ED 29 D0 99 31 D0 29 D0 C3
The above code defines a function that accepts a list/array of integer digits and returns the absolute difference between the sum of its even digits and the sum of its odd digits.
Wie in C, assembly language doesn't implement lists or arrays as first-class types, but rather represents them as a combination of a pointer and a length. Therefore, I have arranged for this function to accept two parameters: the first is a pointer to the beginning of the list of digits, and the second is an integer specifying the total length of the list (total number of digits, one-indexed).
Die Funktion entspricht der System V AMD64-Aufrufkonvention , die auf Gnu / UNIX-Systemen Standard ist. Insbesondere wird der erste Parameter (Zeiger auf den Anfang der Liste) übergeben RDI
(da dies ein 64-Bit-Code ist, handelt es sich um einen 64-Bit-Zeiger), und der zweite Parameter (Länge der Liste) wird übergeben ESI
( Dies ist nur ein 32-Bit-Wert, da das mehr als genug Stellen sind, um damit zu spielen, und natürlich wird davon ausgegangen, dass es nicht Null ist. Das Ergebnis wird im EAX
Register zurückgegeben.
Wenn es klarer ist, wäre dies der C-Prototyp (und Sie können dies verwenden, um die Funktion von C aus aufzurufen):
int OddsAndEvens(int *ptrDigits, int length);
Ungolfed Assembler-Mnemonik:
; parameter 1 (RDI) == pointer to list of integer digits
; parameter 2 (ESI) == number of integer digits in list (assumes non-zero, of course)
OddsAndEvens:
xor eax, eax ; EAX = 0 (accumulator for evens)
cdq ; EDX = 0 (accumulator for odds)
.IterateDigits:
mov ecx, [rdi+rsi*4-4] ; load next digit from list
test cl, 1 ; test last bit to see if even or odd
jz .IsEven ; jump if last bit == 0 (even)
.IsOdd: ; fall through if last bit != 0 (odd)
add edx, ecx ; add value to odds accumulator
jmp .Continue ; keep looping
.IsEven:
add eax, ecx ; add value to evens accumulator
.Continue: ; fall through
dec esi ; decrement count of digits in list
jnz .IterateDigits ; keep looping as long as there are digits left
sub eax, edx ; subtract odds accumulator from evens accumulator
; abs
cdq ; sign-extend EAX into EDX
xor eax, edx ; XOR sign bit in with the number
sub eax, edx ; subtract sign bit
ret ; return with final result in EAX
Hier ist eine kurze Einführung in den Code:
- Zuerst setzen wir die
EAX
und -Register auf Null, die EDX
verwendet werden, um die Gesamtsummen von geraden und ungeraden Ziffern zu halten. Das EAX
Register wird gelöscht, indem XOR
es mit sich selbst (2 Bytes) EDX
gelöscht wird , und dann wird das Register gelöscht, indem der EAX mit Vorzeichen darin erweitert wird (CDQ
1 Byte) .
Dann gehen wir in die Schleife, die alle im Array übergebenen Ziffern durchläuft. Es ruft eine Ziffer ab, prüft, ob sie gerade oder ungerade ist (indem es das niedrigstwertige Bit prüft, das 0 ist, wenn der Wert gerade ist, oder 1, wenn es ungerade ist), und springt oder fällt dann entsprechend durch und fügt dies hinzu Wert auf den entsprechenden Akku. Am Ende der Schleife dekrementieren wir den Ziffernzähler (ESI
) und setzen die Schleife fort, solange er nicht Null ist (dh solange noch weitere Ziffern in der abzurufenden Liste vorhanden sind).
Das einzige, was hier schwierig ist, ist der anfängliche MOV-Befehl, der den komplexesten Adressierungsmodus verwendet, der auf x86 möglich ist. * Es nimmt RDI
als Basisregister (den Zeiger zum Anfang der Liste), skaliert RSI
(den Längenzähler, der als Index dient) um 4 (die Größe einer Ganzzahl in Bytes) und fügt das zur Basis hinzu, und dann subtrahiert man 4 von der Summe (weil der Längenzähler einsbasiert ist und der Offset nullbasiert sein muss). Dies gibt die Adresse der Ziffer im Array an, die dann in das ECX
Register geladen wird .
Nachdem die Schleife beendet ist, subtrahieren wir die Gewinnchancen von den Evens ( EAX -= EDX
).
Schließlich berechnen wir den Absolutwert mit einem allgemeinen Trick, der auch von den meisten C-Compilern für die abs
Funktion verwendet wird. Ich werde hier nicht näher darauf eingehen, wie dieser Trick funktioniert. In den Codekommentaren finden Sie Hinweise, oder führen Sie eine Websuche durch.
__
* Der Code kann umgeschrieben werden, um einfachere Adressierungsmodi zu verwenden, verkürzt ihn jedoch nicht. Ich konnte mir eine alternative Implementierung einfallen lassen, die die Referenz RDI
jedes Mal um 8 dereferenzierte und inkrementierte, aber da Sie den Zähler immer noch dekrementieren müssen ESI
, stellte sich heraus, dass dies die gleichen 30 Bytes waren. Was mir anfangs Hoffnung gegeben hatte, ist, dass add eax, DWORD PTR [rdi]
es nur 2 Bytes sind, was dem Hinzufügen von zwei registrierten Werten entspricht. Hier ist diese Implementierung, wenn auch nur, um jedem zu ersparen, der versucht, mich zu übertreiben :-)
OddsAndEvens_Alt:
31 C0 xor eax, eax
99 cdq
.IterateDigits:
F6 07 01 test BYTE PTR [rdi], 1
74 04 je .IsEven
.IsOdd:
03 17 add edx, DWORD PTR [rdi]
EB 02 jmp .Continue
.IsEven:
03 07 add eax, DWORD PTR [rdi]
.Continue:
48 83 C7 08 add rdi, 8
FF CE dec esi
75 ED jne .IterateDigits
29 D0 sub eax, edx
99 cdq
31 D0 xor eax, edx
29 D0 sub eax, edx
C3 ret