código de máquina x86-64, 12 bytes para int64_t
entrada
6 bytes para double
entrada
Requer a popcnt
extensão ISA ( CPUID.01H:ECX.POPCNT [Bit 23] = 1
).
(Ou 13 bytes, se a modificação do argumento no local exigir a gravação de todos os 64 bits, em vez de deixar o lixo nos 32 superiores. Acho razoável argumentar que o chamador provavelmente só desejaria carregar os 32b baixos de qualquer maneira e x86 zero - estende de 32 a 64 implicitamente em todas as operações de 32 bits. Ainda assim, impede o chamador de fazer add rbx, [rdi]
algo assim.)
As instruções x87 são mais curtas que o SSE2 cvtsi2sd
/ mais óbvio movq
(usado na resposta do @ ceilingcat ) e um [reg]
modo de endereçamento é do mesmo tamanho que um reg
: apenas um byte mod / rm.
O truque era encontrar uma maneira de passar o valor na memória, sem precisar de muitos bytes para os modos de endereçamento. (por exemplo, passar a pilha não é tão bom.) Felizmente, as regras permitem args de leitura / gravação ou args de saída separados , para que eu possa fazer com que o chamador me passe um ponteiro para a memória que tenho permissão para escrever.
É possível chamar a partir de C com a assinatura: void popc_double(int64_t *in_out);
apenas os 32b baixos do resultado são válidos, o que talvez seja estranho para C, mas natural para asm. (A correção disso requer um prefixo REX no armazenamento final ( mov [rdi], rax
), portanto, mais um byte.) No Windows, altere rdi
para rdx
, pois o Windows não usa a ABI do System V x86-64.
Listagem NASM. O link TIO possui o código fonte sem a desmontagem.
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
Experimente online! Inclui um_start
programa de teste que transmite um valor e sai com exit status = popcnt return value. (Abra a guia "debug" para vê-lo.)
Passar ponteiros de entrada / saída separados também funcionaria (rdi e rsi na ABI do x86-64 SystemV), mas não podemos destruir razoavelmente a entrada de 64 bits ou justificar facilmente a necessidade de um buffer de saída de 64 bits enquanto apenas grava o baixo 32b.
Se quisermos argumentar que podemos pegar um ponteiro para o inteiro de entrada e destruí-lo, enquanto retornamos a saída rax
, simplesmente omita o mov [rdi], eax
from popcnt_double_outarg
, diminuindo-o para 10 bytes.
Alternativa sem truques bobos de convenções de chamada, 14 bytes
use a pilha como espaço de trabalho, push
para chegar lá. Use push
/ pop
para copiar registros em 2 bytes em vez de 3 para mov rdi, rsp
. ( [rsp]
sempre precisa de um byte SIB, vale a pena gastar 2 bytes para copiar rsp
antes de três instruções que o utilizam.)
Ligue de C com esta assinatura: 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.
Aceitando entrada em double
formato
A questão apenas diz que é um número inteiro em um determinado intervalo, não que ele precise estar em uma representação de número inteiro binário base2. Aceitar double
entrada significa que não há mais sentido usar x87. (A menos que você use uma convenção de chamada personalizada onde double
s são passados nos registros x87. Em seguida, armazene na zona vermelha abaixo da pilha e popcnt a partir daí.)
11 bytes:
57 00000110 66480F7EC0 movq rax, xmm0
58 00000115 F3480FB8C0 popcnt rax, rax
59 0000011A C3 ret
Mas podemos usar o mesmo truque de passagem por referência de antes para criar uma versão de 6 bytes: int pcd(const double&d);
58 00000110 F3480FB807 popcnt rax, [rdi]
59 00000115 C3 ret
6 bytes .
binary64
formato de ponto flutuante , se quiserem? Algumas pessoas (inclusive eu, inicialmente) estavam interpretando a pergunta como exigindo que as funções aceitassem entradas como um tipo inteiro como Cslong
. Em C, você pode argumentar que o idioma será convertido para você, assim como quando você ligasqrt((int)foo)
. Mas existem algumas respostas asm de código de máquina x86 (como codegolf.stackexchange.com/a/136360/30206 e mine) que ambas estavam assumindo que precisávamos aceitar entradas inteiras de 64 bits. Aceitar umbinary64
valor economizaria 5 bytes.