Código de 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
El código anterior define una función que acepta una lista / matriz de dígitos enteros y devuelve la diferencia absoluta entre la suma de sus dígitos pares y la suma de sus dígitos impares.
Como en C , el lenguaje ensamblador no implementa listas o matrices como tipos de primera clase, sino que las representa como una combinación de un puntero y una longitud. Por lo tanto, he dispuesto que esta función acepte dos parámetros: el primero es un puntero al comienzo de la lista de dígitos, y el segundo es un número entero que especifica la longitud total de la lista (número total de dígitos, un índice) .
La función se ajusta a la convención de llamadas System V AMD64 , que es estándar en los sistemas Gnu / UNIX. En particular, se pasa el primer parámetro (puntero al comienzo de la lista) RDI
(como se trata de un código de 64 bits, es un puntero de 64 bits) y se pasa el segundo parámetro (longitud de la lista) ESI
( este es solo un valor de 32 bits, porque son dígitos más que suficientes para jugar, y naturalmente se supone que no es cero). El resultado se devuelve en el EAX
registro.
Si es más claro, este sería el prototipo C (y puede usar esto para llamar a la función desde C):
int OddsAndEvens(int *ptrDigits, int length);
Mnemónicos de ensamblaje sin golf:
; 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
Aquí hay un breve recorrido por el código:
- Primero, ponemos a cero los registros
EAX
y EDX
, que se usarán para contener los totales de suma de dígitos pares e impares. El EAX
registro se borra XOR
haciéndolo consigo mismo (2 bytes), y luego EDX
se borra al extender el signo EAX ( CDQ
1 byte).
Luego, entramos en el ciclo que itera a través de todos los dígitos pasados en la matriz. Recupera un dígito, prueba para ver si es par o impar (probando el bit menos significativo, que será 0 si el valor es par o 1 si es impar), y luego salta o cae en consecuencia, agregando que valor para el acumulador apropiado. En la parte inferior del bucle, disminuimos el contador de dígitos ( ESI
) y continuamos haciendo el bucle siempre que no sea cero (es decir, mientras haya más dígitos restantes en la lista para recuperar).
Lo único complicado aquí es la instrucción MOV inicial, que utiliza el modo de direccionamiento más complejo posible en x86. * Toma RDI
como registro base (el puntero al comienzo de la lista), escala RSI
(el contador de longitud, que sirve como índice) en 4 (el tamaño de un entero, en bytes) y lo agrega a la base, y luego resta 4 del total (porque el contador de longitud está basado en uno y necesitamos que el desplazamiento esté basado en cero). Esto proporciona la dirección del dígito en la matriz, que luego se carga en el ECX
registro.
Después de que el ciclo ha terminado, hacemos la resta de las probabilidades de los pares ( EAX -= EDX
).
Finalmente, calculamos el valor absoluto utilizando un truco común, el mismo utilizado por la mayoría de los compiladores de C para la abs
función. No entraré en detalles sobre cómo funciona este truco aquí; vea los comentarios del código para obtener sugerencias o realice una búsqueda en la web.
__
* El código puede reescribirse para usar modos de direccionamiento más simples, pero no lo hace más corto. Pude idear una implementación alternativa que la desreferenciaba RDI
y la incrementaba en 8 cada vez a través del ciclo, pero debido a que aún tiene que disminuir el contador ESI
, resultaron ser los mismos 30 bytes. Lo que inicialmente me había dado esperanza es que add eax, DWORD PTR [rdi]
solo son 2 bytes, lo mismo que agregar dos valores registrados. Aquí está esa implementación, aunque solo sea para salvar a cualquiera que intente superar mi esfuerzo :-)
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