x86-64 Машинний код, 24 байти
6A 0A 5E 31 C9 89 F8 99 F7 F6 01 D1 85 C0 75 F7 8D 04 09 99 F7 F7 92 C3
Вищевказаний код визначає функцію в 64-розрядному машинному коді x86, яка визначає, чи вхідне значення ділиться на подвійну суму його цифр. Функція відповідає умові виклику System V AMD64, так що її можна дзвонити практично з будь-якої мови, як би це була функція C.
Він приймає один параметр як вхід через EDI
регістр, згідно з умовою виклику, що є цілим числом для тестування. (Передбачається, що це натуральне число, відповідне правилам виклику, і воно потрібне для CDQ
інструкції, яку ми використовуємо для правильної роботи.)
EAX
Знову він повертає свій результат у регістр, відповідно до конвенції, що викликає. Результат буде 0, якщо вхідне значення було розділене на суму його цифр, а не нульове в іншому випадку. (В основному, зворотний булевий, точно як приклад, наведений у правилах виклику.)
Його прототипом C було б:
int DivisibleByDoubleSumOfDigits(int value);
Ось невідомі інструкції з мови збірки, анотація з коротким поясненням мети кожної інструкції:
; 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
У першому блоці робимо попередню ініціалізацію регістрів:
PUSH
+ POP
інструкції використовуються як повільний, але короткий спосіб ініціалізації ESI
до 10. Це необхідно, оскільки DIV
інструкція на x86 вимагає операнду реєстру. (Немає форми, яка ділиться на безпосереднє значення, скажімо, 10.)
XOR
використовується як короткий і швидкий спосіб очищення ECX
реєстру. Цей регістр буде служити "акумулятором" всередині майбутнього циклу.
- Нарешті, робиться копія вхідного значення (від
EDI
) і зберігається в ньому EAX
, який буде клобуватися, коли ми проходимо цикл.
Потім ми починаємо циклічне підсумовування та підсумовування цифр у вхідному значенні. Це засновано на DIV
інструкції x86 , яка ділиться EDX:EAX
за операндом і повертає коефіцієнт на, EAX
а решту в EDX
. Що ми тут зробимо, це розділити вхідне значення на 10, таким чином, що залишок - це цифра на останньому місці (яку ми додамо до нашого реєстру акумуляторів ECX
), а коефіцієнт - цифри, що залишилися.
CDQ
Інструкція короткий шлях установки EDX
0. Це фактично знаково-розширює значення в EAX
до EDX:EAX
, що і DIV
використовує в якості дивідендів. Тут насправді не потрібно розширення знаків, оскільки вхідне значення не підписане, але CDQ
це 1 байт, на відміну від використання XOR
для очищення EDX
, яке було б 2 байти.
- Тоді ми
DIV
іде EDX:EAX
по ESI
(10).
- Залишок (
EDX
) додається до акумулятора ( ECX
).
EAX
Регістр (фактор) перевіряється , щоб побачити , якщо він дорівнює 0. Якщо це так, ми зробили це через всі цифри , і ми провалитися. Якщо ні, у нас ще є цифри для підсумовування, тому ми повертаємося до вершини циклу.
Нарешті, після закінчення циклу ми реалізуємо number % ((sum_of_digits)*2)
:
LEA
Інструкція використовується як короткий спосіб помножити ECX
на 2 (або, що те ж саме, додати ECX
до себе), і зберегти результат в іншому регістрі (в даному випадку, EAX
).
(Ми також могли зробити add ecx, ecx
+ xchg ecx, eax
; обидва - 3 байти, але LEA
інструкція швидша і типовіша.)
- Потім робимо ще
CDQ
раз, щоб підготуватися до поділу. Оскільки EAX
буде позитивним (тобто непідписаним), це матиме ефект нулю EDX
, як і раніше.
- Далі йде поділ, цей час ділиться
EDX:EAX
на вхідне значення (непомітна копія якого все ще знаходиться в EDI
). Це еквівалентно модулю, а решта в EDX
. (Коефіцієнт також вводиться EAX
, але він нам не потрібен.)
- Нарешті, ми
XCHG
(обмінюємось) вмістом EAX
та EDX
. Зазвичай ви робите MOV
тут, але XCHG
це лише 1 байт (хоч і повільніше). Оскільки EDX
міститься залишок після поділу, він буде дорівнює 0, якщо значення було рівномірно розділене або ненульове в іншому випадку. Таким чином, коли ми робимо RET
урну, EAX
(результат) дорівнює 0, якщо вхідне значення було розділене на подвійну суму його цифр, або не нульове в іншому випадку.
Сподіваємось, цього достатньо для пояснення.
Це не найкоротший запис, але ей, схоже, він перемагає майже всі мови, що не гольфують! :-)