Mã máy x86-64, 24 byte
6A 0A 5E 31 C9 89 F8 99 F7 F6 01 D1 85 C0 75 F7 8D 04 09 99 F7 F7 92 C3
Đoạn mã trên xác định một hàm trong mã máy 64 bit x86 để xác định xem giá trị đầu vào có chia hết cho gấp đôi tổng các chữ số của nó hay không. Hàm này tuân theo quy ước gọi AMD64 của System V, do đó nó có thể gọi được từ hầu hết mọi ngôn ngữ, giống như là chức năng C.
Nó nhận một tham số duy nhất làm đầu vào thông qua thanh EDI
ghi, theo quy ước gọi, là số nguyên để kiểm tra. (Điều này được giả định là một tích cực số nguyên, phù hợp với các quy tắc thách thức, và là cần thiết cho CDQ
giảng dạy, chúng tôi sử dụng để làm việc một cách chính xác.)
Nó trả về kết quả của nó trong thanh EAX
ghi, một lần nữa, theo quy ước gọi. Kết quả sẽ là 0 nếu giá trị đầu vào là chia hết cho tổng các chữ số của nó, và không-zero khác. (Về cơ bản, một Boolean nghịch đảo, giống hệt như ví dụ được đưa ra trong các quy tắc thử thách.)
Nguyên mẫu C của nó sẽ là:
int DivisibleByDoubleSumOfDigits(int value);
Dưới đây là các hướng dẫn ngôn ngữ lắp ráp không được chỉnh sửa, được chú thích với một lời giải thích ngắn gọn về mục đích của mỗi hướng dẫn:
; 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
Trong khối đầu tiên, chúng tôi thực hiện một số khởi tạo sơ bộ của các thanh ghi:
PUSH
+ POP
hướng dẫn được sử dụng như một cách chậm nhưng ngắn để khởi tạo ESI
thành 10. Điều này là cần thiết bởi vì DIV
hướng dẫn trên x86 yêu cầu một toán hạng đăng ký. (Không có hình thức nào chia cho giá trị ngay lập tức của, giả sử, 10.)
XOR
được sử dụng như một cách ngắn và nhanh chóng để xóa ECX
sổ đăng ký. Thanh ghi này sẽ đóng vai trò là "bộ tích lũy" bên trong vòng lặp sắp tới.
- Cuối cùng, một bản sao của giá trị đầu vào (từ
EDI
) được tạo ra và được lưu trữ trong EAX
đó sẽ bị ghi đè khi chúng ta đi qua vòng lặp.
Sau đó, chúng tôi bắt đầu lặp và tổng hợp các chữ số trong giá trị đầu vào. Điều này dựa trên lệnh x86 DIV
, chia EDX:EAX
cho toán hạng của nó và trả về thương số trong EAX
và phần còn lại trong EDX
. Những gì chúng ta sẽ làm ở đây là chia giá trị đầu vào cho 10, sao cho phần còn lại là chữ số ở vị trí cuối cùng (chúng ta sẽ thêm vào thanh ghi tích lũy của mình, ECX
) và thương số là các chữ số còn lại.
- Các
CDQ
hướng dẫn là một cách ngắn thiết EDX
để 0. Nó thực sự đăng ký mở rộng giá trị trong EAX
để EDX:EAX
, đó là những gì DIV
sử dụng như số bị chia. Chúng tôi thực sự không cần phần mở rộng đăng nhập ở đây, vì giá trị đầu vào không được ký, nhưng CDQ
là 1 byte, trái ngược với việc sử dụng XOR
để xóa EDX
, sẽ là 2 byte.
- Sau đó, chúng tôi
DIV
ide EDX:EAX
bởi ESI
(10).
- Phần còn lại (
EDX
) được thêm vào bộ tích lũy ( ECX
).
- Thanh
EAX
ghi (thương số) được kiểm tra để xem nó có bằng 0. Nếu vậy, chúng tôi đã thực hiện thông qua tất cả các chữ số và chúng tôi rơi qua. Nếu không, chúng ta vẫn có nhiều chữ số để tính tổng, vì vậy chúng ta quay lại đầu vòng lặp.
Cuối cùng, sau khi vòng lặp kết thúc, chúng tôi thực hiện number % ((sum_of_digits)*2)
:
Các LEA
hướng dẫn được sử dụng như một cách ngắn để nhân ECX
bởi 2 (hoặc tương đương, thêm ECX
vào chính nó), và lưu trữ kết quả trong một thanh ghi khác nhau (trong trường hợp này, EAX
).
(Chúng tôi cũng có thể đã thực hiện add ecx, ecx
+ xchg ecx, eax
; cả hai đều là 3 byte, nhưng LEA
hướng dẫn nhanh hơn và điển hình hơn.)
- Sau đó, chúng tôi làm một
CDQ
lần nữa để chuẩn bị cho sự phân chia. Bởi vì EAX
sẽ là tích cực (tức là không dấu), điều này có tác dụng của zeroing EDX
, giống như trước đây.
- Tiếp theo là sự phân chia, lần này chia
EDX:EAX
cho giá trị đầu vào (một bản sao không bị biến dạng vẫn còn tồn tại EDI
). Điều này tương đương với modulo, với phần còn lại trong EDX
. (Thương số cũng được đưa vào EAX
, nhưng chúng tôi không cần nó.)
- Cuối cùng, chúng tôi
XCHG
(trao đổi) nội dung của EAX
và EDX
. Thông thường, bạn sẽ làm MOV
ở đây, nhưng XCHG
chỉ 1 byte (mặc dù chậm hơn). Vì EDX
chứa phần còn lại sau khi chia, nó sẽ là 0 nếu giá trị chia đều hoặc khác không. Do đó, khi chúng ta khởi động RET
, EAX
(kết quả) là 0 nếu giá trị đầu vào chia hết cho gấp đôi tổng các chữ số của nó, hoặc khác không.
Hy vọng rằng đủ cho một lời giải thích.
Đây không phải là mục ngắn nhất, nhưng này, có vẻ như nó đánh bại hầu hết các ngôn ngữ không chơi gôn! :-)