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