машинний код x86-64, 12 байт для int64_tвведення
6 байт для doubleвведення
Потрібно popcntрозширення ISA ( CPUID.01H:ECX.POPCNT [Bit 23] = 1).
(Або 13 байт, якщо для зміни аргументу на місці потрібно записати всі 64-бітні замість того, щоб залишати сміття у верхній частині 32. Я думаю, що розумно стверджувати, що абонент, ймовірно, хотів би завантажити хоч низький 32b, і нуль x86 -розширюється від 32 до 64 неявно під час кожної 32-бітної операції, але це не зупиняє абонента робити add rbx, [rdi] чи щось подібне.)
Інструкції x87 коротші, ніж більш очевидна SSE2 cvtsi2sd/ movq(використовується у відповіді на @ roofcat ), а [reg]режим адресації має той самий розмір, що і reg: лише байт mod / rm.
Трюк полягав у тому, щоб придумати спосіб передавати значення в пам'яті, не потребуючи занадто багато байтів для адресних режимів. (наприклад, передача стека не така вже й велика.) На щастя, правила дозволяють аргументи для читання / запису або окремі вихідні аргументи , тому я можу просто заставити абонента передати мені вказівник на пам'ять, яку мені дозволяється писати.
Зателефонувавши з C з підписом: void popc_double(int64_t *in_out); Дійсно лише низький 32b результату, що може бути дивним для C, але природним для asm. (Для виправлення цього потрібен префікс REX на остаточному сховищі ( mov [rdi], rax), тому ще один байт.) У Windows змініть rdiнаrdx , оскільки Windows не використовує x86-64 System V ABI.
Список NASM. Посилання TIO має вихідний код без розбирання.
1 addr machine global popcnt_double_outarg
2 code popcnt_double_outarg:
3 ;; normal x86-64 ABI, or x32: void pcd(int64_t *in_out)
4 00000000 DF2F fild qword [rdi] ; int64_t -> st0
5 00000002 DD1F fstp qword [rdi] ; store binary64, using retval as scratch space.
6 00000004 F3480FB807 popcnt rax, [rdi]
7 00000009 8907 mov [rdi], eax ; update only the low 32b of the in/out arg
8 0000000B C3 ret
# ends at 0x0C = 12 bytes
Спробуйте в Інтернеті! Включає_start тестову програму, яка передає їй значення і закінчується статусом виходу = повернення popcnt. (Відкрийте вкладку "налагодження", щоб побачити її.)
Передача окремих покажчиків вводу / виводу також спрацювала (rdi та rsi в x86-64 SystemV ABI), але тоді ми не можемо розумно знищити 64-бітний вхід або як легко обгрунтувати необхідність 64-бітового вихідного буфера під час запису лише низький 32b.
Якщо ми хочемо стверджувати, що ми можемо взяти покажчик на вхідне ціле число і знищити його, повертаючи при цьому вихід rax, тоді просто опустімо mov [rdi], eaxз popcnt_double_outarg, знизивши його до 10 байт.
Альтернатива без дурних викликів-конвенцій, 14 байт
використовувати стек як місце для нуля, pushщоб дістати його туди. Використовуйте push/ popдля копіювання регістрів у 2 байти замість 3 для mov rdi, rsp. ( [rsp]завжди потрібен байт SIB, тому варто витратити 2 байти на копіювання rspперед трьома інструкціями, які ним користуються.)
Телефонуйте з C з цим підписом: int popcnt_double_push(int64_t);
11 global popcnt_double_push
12 popcnt_double_push:
13 00000040 57 push rdi ; put the input arg on the stack (still in binary integer format)
14 00000041 54 push rsp ; pushes the old value (rsp updates after the store).
15 00000042 5A pop rdx ; mov rdx, rsp
16 00000043 DF2A fild qword [rdx]
17 00000045 DD1A fstp qword [rdx]
18 00000047 F3480FB802 popcnt rax, [rdx]
19 0000004C 5F pop rdi ; rebalance the stack
20 0000004D C3 ret
next byte is 0x4E, so size = 14 bytes.
Прийом введення у doubleформаті
Питання просто говорить про те, що це ціле число в певному діапазоні, а не те, що воно повинно бути в двійковому цілому поданні base2. Прийняття doubleвводу означає, що більше немає сенсу використовувати x87. (Якщо ви не використовуєте користувацьку конвенцію про виклики, коли doubles передаються в регістри x87. Потім зберігайте в червоній зоні під стеком і виконайте popcnt.)
11 байт:
57 00000110 66480F7EC0 movq rax, xmm0
58 00000115 F3480FB8C0 popcnt rax, rax
59 0000011A C3 ret
Але ми можемо використовувати той самий трюк проходження посилання, як і раніше, щоб зробити 6-байтну версію: int pcd(const double&d);
58 00000110 F3480FB807 popcnt rax, [rdi]
59 00000115 C3 ret
6 байт .
binary64форматі з плаваючою комою , якщо вони хочуть? Деякі люди ( в тому числі себе, спочатку) інтерпретували питання, вимагаючи , щоб функції приймають вхідні сигнали як цілого типу , як C - хlong. На мові C ви можете стверджувати, що мова буде конвертувати для вас, як і коли ви телефонуєтеsqrt((int)foo). Але є кілька відповідей машинного коду на x86 (як codegolf.stackexchange.com/a/136360/30206 і моя), які припускали, що нам доведеться приймати 64-бітні цілі числа. Прийняттяbinary64значення дозволить заощадити 5 байт.