x86-64 Codice macchina, 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
Il codice precedente definisce una funzione che accetta un elenco / array di cifre intere e restituisce la differenza assoluta tra la somma delle sue cifre pari e la somma delle sue cifre dispari.
Come in C , il linguaggio assembly non implementa elenchi o matrici come tipi di prima classe, ma li rappresenta piuttosto come una combinazione di un puntatore e una lunghezza. Pertanto, ho predisposto che questa funzione accetti due parametri: il primo è un puntatore all'inizio dell'elenco di cifre e il secondo è un numero intero che specifica la lunghezza totale dell'elenco (numero totale di cifre, uno indicizzato) .
La funzione è conforme alla convenzione di chiamata System V AMD64 , che è standard sui sistemi Gnu / UNIX. In particolare, viene passato il primo parametro (puntatore all'inizio dell'elenco) RDI
(poiché si tratta di un codice a 64 bit, è un puntatore a 64 bit) e viene passato il secondo parametro (lunghezza dell'elenco) ESI
( questo è solo un valore a 32 bit, perché è più che sufficiente per giocare, e naturalmente si presume che sia diverso da zero). Il risultato viene restituito nel EAX
registro.
Se è più chiaro, questo sarebbe il prototipo C (e puoi usarlo per chiamare la funzione da C):
int OddsAndEvens(int *ptrDigits, int length);
Mnemonici di assemblaggio non golfati:
; 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
Ecco una breve descrizione del codice:
- Innanzitutto, azzeriamo i registri
EAX
e EDX
, che verranno utilizzati per contenere i totali delle cifre pari e dispari. Il EAX
registro viene cancellato XOR
inserendolo con se stesso (2 byte), quindi il EDX
registro viene cancellato estendendo il segno EAX al suo interno ( CDQ
, 1 byte).
Quindi, entriamo nel ciclo che scorre tutte le cifre passate nell'array. Recupera una cifra, verifica se è pari o dispari (testando il bit meno significativo, che sarà 0 se il valore è pari o 1 se è dispari), quindi salta o cade di conseguenza, aggiungendo che valore per l'accumulatore appropriato. Nella parte inferiore del loop, decrementiamo il contatore delle cifre ( ESI
) e continuiamo il loop fino a quando è diverso da zero (ovvero, purché ci siano più cifre rimaste nell'elenco da recuperare).
L'unica cosa difficile qui è l'istruzione MOV iniziale, che utilizza la modalità di indirizzamento più complessa possibile su x86. * Prende RDI
come registro di base (il puntatore all'inizio dell'elenco), ridimensiona RSI
(il contatore della lunghezza, che funge da indice) di 4 (la dimensione di un numero intero, in byte) e lo aggiunge alla base, e quindi sottrae 4 dal totale (poiché il contatore della lunghezza è a base singola e abbiamo bisogno che l'offset sia a base zero). Ciò fornisce l'indirizzo della cifra nell'array, che viene quindi caricato nel ECX
registro.
Dopo che il ciclo è terminato, eseguiamo la sottrazione delle probabilità da evens ( EAX -= EDX
).
Infine, calcoliamo il valore assoluto usando un trucco comune, lo stesso usato dalla maggior parte dei compilatori C per la abs
funzione. Non entrerò nei dettagli su come funziona questo trucco qui; vedere i commenti sul codice per suggerimenti o fare una ricerca sul web.
__
* Il codice può essere riscritto per utilizzare modalità di indirizzamento più semplici, ma non lo rende più breve. Sono stato in grado di escogitare un'implementazione alternativa che lo dereferenziasse RDI
e lo incrementasse di 8 ogni volta attraverso il ciclo, ma poiché devi ancora diminuire il contatore ESI
, questo si è rivelato essere gli stessi 30 byte. Ciò che inizialmente mi aveva dato la speranza è che fossero add eax, DWORD PTR [rdi]
solo 2 byte, lo stesso che aggiungere due valori registrati. Ecco l'implementazione, se non altro per salvare qualcuno che tenta di superarmi un po 'di sforzo :-)
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