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
clear 相对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,否则为非零。
希望这足以解释。
这不是最短的条目,但是,嘿,它似乎击败了几乎所有非高尔夫语言!:-)