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 的缓慢但简短的方法。这是必需的,因为DIVx86上的指令需要寄存器操作数。(没有任何形式可以除以10的立即值。)
XOR用作清除ECX寄存器的快速方法。该寄存器将在即将到来的循环中充当“累加器”。
- 最后,制作输入值(来自
EDI)的副本,并将其存储在中EAX,当我们进行循环时,它将被破坏。
然后,我们开始循环并求和输入值中的数字。这基于x86 DIV指令,该指令除EDX:EAX以其操作数,然后返回中的商EAX和中的余数EDX。我们在这里要做的是将输入值除以10,以便余数是最后一位(我们将其添加到累加器寄存器中ECX),而商就是余数。
- 该
CDQ指令是将其设置EDX为0的一种简短方法。它实际上将值符号扩展EAX为EDX:EAX,这是DIV用作被除数的方法。我们实际上不需要在这里进行符号扩展,因为输入值是无符号的,但是CDQ是1字节,与使用XORclear 相对EDX,这是2字节。
- 然后我们
DIVIDE EDX:EAX由ESI(10)。
- 余数(
EDX)添加到累加器(ECX)中。
- 测试
EAX寄存器(商)以查看它是否等于0。如果是,那么我们已经遍历所有数字,并且失败了。如果不是,我们还有更多的数字可以累加,所以我们回到循环的顶部。
最后,在循环完成之后,我们实现number % ((sum_of_digits)*2):
该LEA指令用作乘以ECX2(或等效地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,否则为非零。
希望这足以解释。
这不是最短的条目,但是,嘿,它似乎击败了几乎所有非高尔夫语言!:-)