Kod maszynowy x86-64, 24 bajty
6A 0A 5E 31 C9 89 F8 99 F7 F6 01 D1 85 C0 75 F7 8D 04 09 99 F7 F7 92 C3
Powyższy kod definiuje funkcję w 64-bitowym kodzie maszynowym x86, który określa, czy wartość wejściowa jest podzielna przez dwukrotność sumy jej cyfr. Funkcja jest zgodna z konwencją wywoływania AMD64 w Systemie V, dzięki czemu można ją wywoływać z praktycznie dowolnego języka, tak jakby była funkcją C.
Pobiera pojedynczy parametr jako dane wejściowe przez EDI
rejestr, zgodnie z konwencją wywoływania, która jest liczbą całkowitą do przetestowania. (Zakłada się, że jest to dodatnia liczba całkowita, zgodna z regułami wyzwania i jest wymagana do prawidłowego działania CDQ
instrukcji, których używamy).
EAX
Ponownie zwraca wynik do rejestru zgodnie z konwencją wywoływania. Wynik będzie wynosił 0, jeśli wartość wejściowa była podzielna przez sumę jej cyfr, a w przeciwnym razie niezerowa. (Zasadniczo odwrotna wartość logiczna, dokładnie taka jak w przykładzie podanym w regułach wyzwania).
Jego prototypem C będzie:
int DivisibleByDoubleSumOfDigits(int value);
Oto instrukcje języka asemblera bez oznakowania, opatrzone krótkim objaśnieniem celu każdej instrukcji:
; 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
W pierwszym bloku dokonujemy wstępnej inicjalizacji rejestrów:
PUSH
+ POP
instrukcje są używane jako powolna, ale krótka droga do inicjalizacji ESI
do 10. Jest to konieczne, ponieważ DIV
instrukcja na x86 wymaga operandu rejestru. (Nie ma formy, która dzieli bezpośrednią wartość, powiedzmy, 10).
XOR
służy jako krótki i szybki sposób na wyczyszczenie ECX
rejestru. Rejestr ten będzie służył jako „akumulator” wewnątrz nadchodzącej pętli.
- Na koniec
EDI
tworzona jest kopia wartości wejściowej (z ) i zapisywana w niej EAX
, która zostanie przechwycona podczas przechodzenia przez pętlę.
Następnie zaczynamy zapętlać i sumować cyfry w wartości wejściowej. Jest to oparte na DIV
instrukcji x86 , która dzieli EDX:EAX
przez operand i zwraca iloraz do, EAX
a resztę do EDX
. Tutaj podzielimy wartość wejściową przez 10, tak że reszta to cyfra w ostatnim miejscu (którą dodamy do naszego rejestru akumulatorów ECX
), a iloraz to pozostałe cyfry.
CDQ
Instrukcja jest krótki sposób ustalania EDX
0. To rzeczywiście podpisze-rozciąga wartość w EAX
celu EDX:EAX
, który jest co DIV
wykorzystuje jako dywidendy. W rzeczywistości nie potrzebujemy tutaj rozszerzenia znaku, ponieważ wartość wejściowa jest bez znaku, ale CDQ
wynosi 1 bajt, w przeciwieństwie XOR
do kasowania EDX
, czyli 2 bajty.
- Następnie
DIV
ide EDX:EAX
przez ESI
(10).
- Pozostała część (
EDX
) jest dodawana do akumulatora ( ECX
).
EAX
Rejestr (iloraz) jest testowany, aby sprawdzić, czy jest równa 0. Jeśli tak, zrobiliśmy to przez wszystkie cyfry i upadamy wskroś. Jeśli nie, nadal mamy więcej cyfr do podsumowania, więc wracamy na początek pętli.
Wreszcie po zakończeniu pętli implementujemy number % ((sum_of_digits)*2)
:
LEA
Instrukcja służy jako krótki sposób pomnożyć ECX
przez 2 (lub równoważnie dodać ECX
do siebie) i zapisać wynik w innym rejestrze (w tym przypadku EAX
).
(Moglibyśmy także zrobić add ecx, ecx
+ xchg ecx, eax
; oba mają 3 bajty, ale LEA
instrukcja jest szybsza i bardziej typowa).
- Następnie robimy jeszcze
CDQ
raz, aby przygotować się do podziału. Ponieważ EAX
będzie dodatni (tj. Niepodpisany), spowoduje to zerowanie EDX
, tak jak poprzednio.
- Następnie jest dzielenie, tym razem dzielone
EDX:EAX
przez wartość wejściową (w której znajduje się niezaburzona kopia EDI
). Jest to równoważne modulo, z resztą w EDX
. (Podany jest również iloraz EAX
, ale nie jest nam potrzebny.)
- Wreszcie
XCHG
(wymieniamy) zawartość EAX
i EDX
. Normalnie zrobiłbyś MOV
tutaj, ale XCHG
ma on tylko 1 bajt (choć wolniej). Ponieważ EDX
zawiera resztę po dzieleniu, będzie wynosić 0, jeśli w przeciwnym razie wartość będzie podzielna równo lub niezerowa. Kiedy więc RET
urnujemy, EAX
(wynik) wynosi 0, jeśli wartość wejściowa była podzielna przez dwukrotność sumy jej cyfr, lub w przeciwnym razie niezerowa.
Mam nadzieję, że to wystarczy do wyjaśnienia.
To nie jest najkrótszy wpis, ale hej, wygląda na to, że bije prawie wszystkie języki inne niż golf! :-)