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 EDIghi, 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 CDQgiả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 EAXghi, 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+ POPhướng dẫn được sử dụng như một cách chậm nhưng ngắn để khởi tạo ESIthành 10. Điều này là cần thiết bởi vì DIVhướ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 ECXsổ đă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:EAXcho toán hạng của nó và trả về thương số trong EAXvà 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
CDQhướ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ì DIVsử 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 CDQlà 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
DIVide EDX:EAXbởi ESI(10).
- Phần còn lại (
EDX) được thêm vào bộ tích lũy ( ECX).
- Thanh
EAXghi (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 LEAhướng dẫn được sử dụng như một cách ngắn để nhân ECXbởi 2 (hoặc tương đương, thêm ECXvà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 LEAhướng dẫn nhanh hơn và điển hình hơn.)
- Sau đó, chúng tôi làm một
CDQlần nữa để chuẩn bị cho sự phân chia. Bởi vì EAXsẽ 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:EAXcho 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 EAXvà EDX. Thông thường, bạn sẽ làm MOVở đây, nhưng XCHGchỉ 1 byte (mặc dù chậm hơn). Vì EDXchứ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! :-)