Código da máquina x86-64, 30 bytes
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
O código acima define uma função que aceita uma lista / matriz de dígitos inteiros e retorna a diferença absoluta entre a soma de seus dígitos pares e a soma de seus dígitos ímpares.
Como em C , a linguagem assembly não implementa listas ou matrizes como tipos de primeira classe, mas os representa como uma combinação de um ponteiro e um comprimento. Portanto, organizei essa função para aceitar dois parâmetros: o primeiro é um ponteiro para o início da lista de dígitos e o segundo é um número inteiro que especifica o comprimento total da lista (número total de dígitos, um indexado) .
A função está em conformidade com a convenção de chamada do System V AMD64 , que é padrão nos sistemas Gnu / UNIX. Em particular, o primeiro parâmetro (ponteiro para o início da lista) é passado RDI
(como esse é um código de 64 bits, é um ponteiro de 64 bits) e o segundo parâmetro (comprimento da lista) é passado ESI
( esse é apenas um valor de 32 bits, porque são dígitos mais do que suficientes para brincar e, naturalmente, supõe-se que seja diferente de zero). O resultado é retornado no EAX
registro.
Se for mais claro, esse seria o protótipo C (e você pode usar isso para chamar a função de C):
int OddsAndEvens(int *ptrDigits, int length);
Mnemônicos de montagem não destruídos:
; 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
Aqui está uma breve explicação do código:
- Primeiro, zeramos os registros
EAX
e EDX
, que serão usados para armazenar a soma dos dígitos pares e ímpares. O EAX
registro é limpo com XOR
ele próprio (2 bytes) e, em seguida, o EDX
registro é limpo com a extensão do sinal do EAX para ele ( CDQ
1 byte).
Em seguida, entramos no loop que itera através de todos os dígitos passados na matriz. Ele recupera um dígito, testa para ver se é par ou ímpar (testando o bit menos significativo, que será 0 se o valor for par ou 1 se for ímpar) e, em seguida, salta ou cai de acordo, acrescentando que valor ao acumulador apropriado. Na parte inferior do loop, decrementamos o contador de dígitos ( ESI
) e continuamos o loop desde que não seja zero (ou seja, desde que haja mais dígitos na lista a serem recuperados).
A única coisa complicada aqui é a instrução MOV inicial, que usa o modo de endereçamento mais complexo possível no x86. * Ele pega RDI
como registro base (o ponteiro para o início da lista), escala RSI
(o contador de comprimento, que serve como índice) por 4 (o tamanho de um número inteiro, em bytes) e o adiciona à base, e subtrai 4 do total (porque o contador de comprimento é baseado em um e precisamos que o deslocamento seja baseado em zero). Isso fornece o endereço do dígito na matriz, que é carregado no ECX
registro.
Depois que o loop termina, fazemos a subtração das probabilidades dos pares ( EAX -= EDX
).
Finalmente, calculamos o valor absoluto usando um truque comum - o mesmo usado pela maioria dos compiladores C para a abs
função. Não vou entrar em detalhes sobre como esse truque funciona aqui; consulte os comentários do código para obter dicas ou faça uma pesquisa na web.
__
* O código pode ser reescrito para usar modos de endereçamento mais simples, mas não o torna mais curto. Consegui criar uma implementação alternativa que a desreferenciou RDI
e incrementou em 8 a cada vez no loop, mas como você ainda precisa diminuir o contador ESI
, isso acabou sendo os mesmos 30 bytes. O que inicialmente me deu esperança é que add eax, DWORD PTR [rdi]
sejam apenas 2 bytes, o mesmo que adicionar dois valores registrados. Aqui está essa implementação, apenas para salvar alguém tentando me superar um pouco de esforço :-)
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