Código de máquina x86-64, 24 bytes
6A 0A 5E 31 C9 89 F8 99 F7 F6 01 D1 85 C0 75 F7 8D 04 09 99 F7 F7 92 C3
El código anterior define una función en el código de máquina x86 de 64 bits que determina si el valor de entrada es divisible por el doble de la suma de sus dígitos. La función se ajusta a la convención de llamadas System V AMD64, por lo que se puede llamar desde prácticamente cualquier lenguaje, como si fuera una función C.
Toma un solo parámetro como entrada a través del EDI
registro, según la convención de llamada, que es el número entero para probar. (Se supone que este es un número entero positivo , consistente con las reglas de desafío, y se requiere para que la CDQ
instrucción que usemos funcione correctamente).
Devuelve su resultado en el EAX
registro, nuevamente, según la convención de llamada. El resultado será 0 si el valor de entrada fue divisible por la suma de sus dígitos, y de lo contrario no será cero. (Básicamente, un booleano inverso, exactamente como el ejemplo dado en las reglas de desafío).
Su prototipo C sería:
int DivisibleByDoubleSumOfDigits(int value);
Aquí están las instrucciones de lenguaje ensamblador no escritas, anotadas con una breve explicación del propósito de cada instrucción:
; EDI == input value
DivisibleByDoubleSumOfDigits:
push 10
pop rsi ; ESI <= 10
xor ecx, ecx ; ECX <= 0
mov eax, edi ; EAX <= EDI (make copy of input)
SumDigits:
cdq ; EDX <= 0
div esi ; EDX:EAX / 10
add ecx, edx ; ECX += remainder (EDX)
test eax, eax
jnz SumDigits ; loop while EAX != 0
lea eax, [rcx+rcx] ; EAX <= (ECX * 2)
cdq ; EDX <= 0
div edi ; EDX:EAX / input
xchg edx, eax ; put remainder (EDX) in EAX
ret ; return, with result in EAX
En el primer bloque, hacemos una inicialización preliminar de registros:
PUSH
Las POP
instrucciones + se usan como una forma lenta pero corta de inicializar ESI
a 10. Esto es necesario porque la DIV
instrucción en x86 requiere un operando de registro. (No existe una forma que se divida por un valor inmediato de, digamos, 10.)
XOR
se usa como una forma corta y rápida de borrar el ECX
registro. Este registro servirá como el "acumulador" dentro del próximo ciclo.
- Finalmente, se hace una copia del valor de entrada (desde
EDI
) y se almacena en ella EAX
, que se registrará a medida que avancemos por el bucle.
Luego, comenzamos a recorrer y sumar los dígitos en el valor de entrada. Esto se basa en la DIV
instrucción x86 , que se divide EDX:EAX
por su operando, y devuelve el cociente EAX
y el resto en EDX
. Lo que haremos aquí es dividir el valor de entrada por 10, de modo que el resto sea el dígito en el último lugar (que agregaremos a nuestro registro de acumulador ECX
), y el cociente son los dígitos restantes.
- La
CDQ
instrucción es una forma corta de establecer EDX
a 0. En realidad, firma-extiende el valor EAX
a EDX:EAX
, que es lo que se DIV
usa como dividendo. En realidad, no necesitamos la extensión de signo aquí, porque el valor de entrada no está firmado, pero CDQ
es de 1 byte, en lugar de usar XOR
para borrarEDX
, que sería de 2 bytes.
- Entonces
DIV
ide EDX:EAX
por ESI
(10).
- El resto (
EDX
) se agrega al acumulador (ECX
).
- El
EAX
registro (el cociente) se prueba para ver si es igual a 0. Si es así, hemos superado todos los dígitos y hemos caído. Si no, todavía tenemos más dígitos para sumar, así que volvemos a la parte superior del ciclo.
Finalmente, una vez finalizado el ciclo, implementamos number % ((sum_of_digits)*2)
:
La LEA
instrucción se utiliza como una forma corta de multiplicar ECX
por 2 (o, equivalentemente, sumar ECX
a sí mismo) y almacenar el resultado en un registro diferente (en este caso EAX
).
(También podríamos haber hecho add ecx, ecx
+ xchg ecx, eax
; ambos son 3 bytes, pero la LEA
instrucción es más rápida y más típica).
- Luego, hacemos otra
CDQ
vez para prepararnos para la división. Como EAX
será positivo (es decir, sin signo), esto tiene el efecto de poner a cero EDX
, como antes.
- Luego está la división, esta vez dividida
EDX:EAX
por el valor de entrada (una copia no molestada de la cual aún reside EDI
). Esto es equivalente al módulo, con el resto adentro EDX
. (El cociente también se incluye EAX
, pero no lo necesitamos).
- Finalmente,
XCHG
(intercambiamos) los contenidos de EAX
y EDX
. Normalmente, harías un MOV
aquí, pero XCHG
es solo 1 byte (aunque más lento). Debido a que EDX
contiene el resto después de la división, será 0 si el valor era igualmente divisible o distinto de cero. Por lo tanto, cuando RET
urnamos, EAX
(el resultado) es 0 si el valor de entrada era divisible por el doble de la suma de sus dígitos, o de lo contrario no sería cero.
Esperemos que eso sea suficiente para una explicación.
Esta no es la entrada más corta, pero bueno, ¡parece que supera a casi todos los idiomas que no son de golf! :-)