Montagem x86, 9 bytes (para entrada concorrente)
Todo mundo que está tentando esse desafio em idiomas de alto nível está perdendo a verdadeira diversão de manipular bits brutos. Há tantas variações sutis nas maneiras de fazer isso, é insano - e muito divertido de se pensar. Aqui estão algumas soluções que eu criei na linguagem assembly x86 de 32 bits.
Peço desculpas antecipadamente por não ser a resposta típica do código-golfe. Vou falar muito sobre o processo de otimização iterativa (por tamanho). Espero que seja interessante e educativo para um público maior, mas se você é do tipo TL; DR, não ficarei ofendido se você pular para o final.
A solução óbvia e eficiente é testar se o valor é ímpar ou par (o que pode ser feito com eficiência observando o bit menos significativo) e depois selecionar entre n + 1 ou n-1 de acordo. Supondo que a entrada seja passada como um parâmetro no ECX
registro e o resultado seja retornado no EAX
registro, obtemos a seguinte função:
F6 C1 01 | test cl, 1 ; test last bit to see if odd or even
8D 41 01 | lea eax, DWORD PTR [ecx + 1] ; set EAX to n+1 (without clobbering flags)
8D 49 FF | lea ecx, DWORD PTR [ecx - 1] ; set ECX to n-1 (without clobbering flags)
0F 44 C1 | cmovz eax, ecx ; move in different result if input was even
C3 | ret
(13 bytes)
Mas, para fins de código-golfe, essas LEA
instruções não são boas, pois levam 3 bytes para codificar. Um simples DEC
arranjo de ECX
seria muito menor (apenas um byte), mas isso afeta os sinalizadores, por isso temos que ser um pouco inteligentes na maneira como organizamos o código. Podemos fazer o decremento primeiro e o teste impar / par em segundo , mas depois temos que inverter o resultado do teste ímpar / par.
Além disso, podemos alterar a instrução de movimentação condicional para uma ramificação, o que pode tornar o código mais lento (dependendo da previsibilidade da ramificação - se a entrada alternar inconsistentemente entre ímpar e par, uma ramificação será mais lenta; se houver uma padrão, será mais rápido), o que nos salvará outro byte.
De fato, com esta revisão, toda a operação pode ser realizada no local, usando apenas um único registro. Isso é ótimo se você estiver incorporando esse código em algum lugar (e as chances são de que você seria, pois é muito curto).
48 | dec eax ; decrement first
A8 01 | test al, 1 ; test last bit to see if odd or even
75 02 | jnz InputWasEven ; (decrement means test result is inverted)
40 | inc eax ; undo the decrement...
40 | inc eax ; ...and add 1
InputWasEven: ; (two 1-byte INCs are shorter than one 3-byte ADD with 2)
(embutido: 7 bytes; como uma função: 10 bytes)
Mas e se você quisesse tornar isso uma função? Nenhuma convenção de chamada padrão usa o mesmo registro para passar parâmetros como para o valor de retorno; portanto, você precisa adicionar uma MOV
instrução de registro no início ou no final da função. Isso praticamente não tem custo em velocidade, mas adiciona 2 bytes. (A RET
instrução também adiciona um byte e há alguma sobrecarga introduzida pela necessidade de fazer e retornar de uma chamada de função, o que significa que este é um exemplo em que o inlining produz um benefício de velocidade e tamanho, em vez de ser apenas uma velocidade clássica - no comércio). No total, escrito como uma função, esse código aumenta para 10 bytes.
O que mais podemos fazer em 10 bytes? Se nos preocupamos com o desempenho (pelo menos, o desempenho previsível ), seria bom nos livrar desse ramo. Aqui está uma solução sem ramificação, com variação de bits do mesmo tamanho em bytes. A premissa básica é simples: usamos um XOR bit a bit para virar o último bit, convertendo um valor ímpar em um par e vice-versa. Mas há uma niggle - para entradas ímpares, que nos dá n-1 , enquanto para entradas pares, nos dá n + 1 - exatamente o oposto do que queremos. Portanto, para corrigir isso, executamos a operação com um valor negativo, lançando efetivamente o sinal.
8B C1 | mov eax, ecx ; copy parameter (ECX) to return register (EAX)
|
F7 D8 | neg eax ; two's-complement negation
83 F0 01 | xor eax, 1 ; XOR last bit to invert odd/even
F7 D8 | neg eax ; two's-complement negation
|
C3 | ret ; return from function
(embutido: 7 bytes; como uma função: 10 bytes)
Muito liso; é difícil ver como isso pode ser melhorado. Uma coisa me chama a atenção, no entanto: essas duas NEG
instruções de 2 bytes . Francamente, dois bytes parecem um byte a mais para codificar uma simples negação, mas é com esse conjunto de instruções que temos que trabalhar. Existem soluções alternativas? Certo! Se XOR
por -2, podemos substituir a segunda NEG
ação por um INC
rement:
8B C1 | mov eax, ecx
|
F7 D8 | neg eax
83 F0 FE | xor eax, -2
40 | inc eax
|
C3 | ret
(embutido: 6 bytes; em função de 9 bytes)
Outra das peculiaridades do conjunto de instruções x86 é a LEA
instrução multiuso , que pode executar um movimento de registro e registro, uma adição de registro e registro, compensando uma constante e dimensionando tudo em uma única instrução!
8B C1 | mov eax, ecx
83 E0 01 | and eax, 1 ; set EAX to 1 if even, or 0 if odd
8D 44 41 FF | lea eax, DWORD PTR [ecx + eax*2 - 1]
C3 | ret
(10 bytes)
A AND
instrução é como a TEST
instrução que usamos anteriormente, em que ambos fazem AND e bit a bit de acordo, mas AND
na verdade atualizam o operando de destino. A LEA
instrução então dimensiona isso em 2, adiciona o valor original de entrada e diminui em 1. Se o valor de entrada for ímpar, isso subtrai 1 (2 × 0 - 1 = -1) dele; se o valor de entrada for par, isso adiciona 1 (2 × 1 - 1 = 1) a ele.
Essa é uma maneira muito rápida e eficiente de escrever o código, já que grande parte da execução pode ser feita no front-end, mas não nos compra muito na forma de bytes, pois são necessárias muitas para codificar um complexo LEA
instrução. Esta versão também não funciona tão bem para fins de inclusão, pois requer que o valor da entrada original seja preservado como uma entrada da LEA
instrução. Portanto, com essa última tentativa de otimização, voltamos ao passado, sugerindo que talvez seja hora de parar.
Portanto, para a entrada final competitiva, temos uma função de 9 bytes que pega o valor de entrada no ECX
registro (uma convenção de chamada semi-padrão baseada em registro em x86 de 32 bits) e retorna o resultado no EAX
registro (como em todas as convenções de chamada x86):
SwapParity PROC
8B C1 mov eax, ecx
F7 D8 neg eax
83 F0 FE xor eax, -2
40 inc eax
C3 ret
SwapParity ENDP
Pronto para montar com MASM; chamada de C como:
extern int __fastcall SwapParity(int value); // MSVC
extern int __attribute__((fastcall)) SwapParity(int value); // GNU