Машинный код 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 , которая будет перекрыта при прохождении цикла.
Затем мы начинаем цикл и суммируем цифры во входном значении. Это основано на x86 DIVинструкции, которая делит EDX:EAXна операнде и возвращает частное в EAXи остаток в EDX. Здесь мы разделим входное значение на 10, так что остаток будет цифрой в последнем месте (которую мы добавим в наш регистр аккумулятора,ECX ), а частное - это оставшиеся цифры.
CDQИнструкция короткий путь установки EDX0. Это фактически знаково-расширяет значение в EAXк EDX:EAX, что и DIVиспользует в качестве дивидендов. На самом деле здесь нам не нужно расширение знака, потому что входное значение является беззнаковым, но CDQсоставляет 1 байт, в отличие от использования XORдля очистки EDX, что составляет 2 байта.
- Тогда мы
DIVIde 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, если значение было равномерно делимым или отличным от нуля в противном случае. Таким образом, когда мы RETurn, EAX(результат) равен 0, если входное значение делится на двойную сумму его цифр, или ненулевое значение в противном случае.
Надеюсь, этого достаточно для объяснения.
Это не самая короткая запись, но, похоже, она превосходит почти все языки без игры в гольф! :-)