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 EDIrejestr, 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 CDQinstrukcji, których używamy).
EAXPonownie 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+ POPinstrukcje są używane jako powolna, ale krótka droga do inicjalizacji ESIdo 10. Jest to konieczne, ponieważ DIVinstrukcja na x86 wymaga operandu rejestru. (Nie ma formy, która dzieli bezpośrednią wartość, powiedzmy, 10).
XORsłuży jako krótki i szybki sposób na wyczyszczenie ECXrejestru. Rejestr ten będzie służył jako „akumulator” wewnątrz nadchodzącej pętli.
- Na koniec
EDItworzona 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 DIVinstrukcji x86 , która dzieli EDX:EAXprzez operand i zwraca iloraz do, EAXa 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.
CDQInstrukcja jest krótki sposób ustalania EDX0. To rzeczywiście podpisze-rozciąga wartość w EAXcelu EDX:EAX, który jest co DIVwykorzystuje jako dywidendy. W rzeczywistości nie potrzebujemy tutaj rozszerzenia znaku, ponieważ wartość wejściowa jest bez znaku, ale CDQwynosi 1 bajt, w przeciwieństwie XORdo kasowania EDX, czyli 2 bajty.
- Następnie
DIVide EDX:EAXprzez ESI(10).
- Pozostała część (
EDX) jest dodawana do akumulatora ( ECX).
EAXRejestr (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):
LEAInstrukcja służy jako krótki sposób pomnożyć ECXprzez 2 (lub równoważnie dodać ECXdo 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 LEAinstrukcja jest szybsza i bardziej typowa).
- Następnie robimy jeszcze
CDQraz, aby przygotować się do podziału. Ponieważ EAXbędzie dodatni (tj. Niepodpisany), spowoduje to zerowanie EDX, tak jak poprzednio.
- Następnie jest dzielenie, tym razem dzielone
EDX:EAXprzez 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ść EAXi EDX. Normalnie zrobiłbyś MOVtutaj, ale XCHGma on tylko 1 bajt (choć wolniej). Ponieważ EDXzawiera resztę po dzieleniu, będzie wynosić 0, jeśli w przeciwnym razie wartość będzie podzielna równo lub niezerowa. Kiedy więc RETurnujemy, 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! :-)