x86 Assembly, 9 byte (per accesso concorrente)
Chiunque stia tentando questa sfida in linguaggi di alto livello si sta perdendo il vero divertimento di manipolare i bit grezzi. Ci sono così tante sottili variazioni sui modi per farlo, è pazzesco e molto divertente a cui pensare. Ecco alcune soluzioni che ho ideato in linguaggio assembly x86 a 32 bit.
Mi scuso in anticipo che questa non è la tipica risposta da golf a codice. Vado in giro molto sul processo di pensiero dell'ottimizzazione iterativa (per dimensioni). Spero che sia interessante ed educativo per un pubblico più vasto, ma se sei il tipo TL; DR, non mi offenderò se salti alla fine.
La soluzione ovvia ed efficiente è verificare se il valore è pari o dispari (cosa che può essere eseguita in modo efficiente osservando il bit meno significativo), quindi selezionare tra n + 1 o n − 1 di conseguenza. Supponendo che l'ingresso sia passato come parametro nel ECXregistro e che il risultato sia restituito nel EAXregistro, otteniamo la seguente funzione:
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 byte)
Ma ai fini del code-golf, queste LEAistruzioni non sono grandiose, poiché richiedono 3 byte per codificare. Un semplice DECrement di ECXsarebbe molto più breve (solo un byte), ma questo influisce sui flag, quindi dobbiamo essere un po 'intelligenti nel modo in cui organizziamo il codice. Possiamo fare il decremento prima , e il pari / dispari di prova secondo , ma poi dobbiamo invertire il risultato del test pari / dispari.
Inoltre, possiamo cambiare l'istruzione di spostamento condizionale in un ramo, il che può far funzionare il codice più lentamente (a seconda di quanto sia prevedibile il ramo - se l'input si alterna in modo incoerente tra pari e dispari, un ramo sarà più lento; se c'è un modello, sarà più veloce), che ci farà risparmiare un altro byte.
In effetti, con questa revisione, l'intera operazione può essere eseguita sul posto, utilizzando un solo registro. Questo è fantastico se stai inserendo questo codice da qualche parte (e probabilmente lo sarai, dato che è così breve).
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)
(in linea: 7 byte; in funzione: 10 byte)
E se volessi renderlo una funzione? Nessuna convenzione di chiamata standard utilizza lo stesso registro per passare i parametri come per il valore restituito, quindi è necessario aggiungere MOVun'istruzione registro-registro all'inizio o alla fine della funzione. Questo non ha praticamente alcun costo in termini di velocità, ma aggiunge 2 byte. (L' RETistruzione aggiunge anche un byte e c'è un certo sovraccarico introdotto dalla necessità di effettuare e tornare da una chiamata di funzione, il che significa che questo è un esempio in cui l'allineamento produce sia un vantaggio di velocità che di dimensione, piuttosto che essere solo una velocità classica -per-spazio tradeoff.) In tutto, scritto come una funzione, questo codice si gonfia a 10 byte.
Cos'altro possiamo fare in 10 byte? Se ci preoccupiamo per le prestazioni (almeno, prestazioni prevedibili ), sarebbe bello sbarazzarsi di quel ramo. Ecco una soluzione priva di ramificazioni di bit che ha la stessa dimensione in byte. La premessa di base è semplice: usiamo un XOR bit a bit per capovolgere l'ultimo bit, convertendo un valore dispari in uno pari e viceversa. Ma c'è un inconveniente - per gli input dispari, che ci dà n-1 , mentre per gli input pari, ci dà n + 1 - esattamente l'opposto di ciò che vogliamo. Quindi, per risolverlo, eseguiamo l'operazione su un valore negativo, capovolgendo efficacemente il segno.
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
(in linea: 7 byte; in funzione: 10 byte)
Abbastanza lucido; è difficile capire come migliorarlo. Una cosa attira la mia attenzione, però: quelle due NEGistruzioni a 2 byte . Francamente, due byte sembrano troppi byte per codificare una semplice negazione, ma questa è l'insieme di istruzioni con cui dobbiamo lavorare. Ci sono soluzioni alternative? Sicuro! Se XORcon -2, possiamo sostituire la seconda NEGazione con un INCrement:
8B C1 | mov eax, ecx
|
F7 D8 | neg eax
83 F0 FE | xor eax, -2
40 | inc eax
|
C3 | ret
(in linea: 6 byte; in funzione: 9 byte)
Un'altra delle stranezze dell'insieme di istruzioni x86 è l' LEAistruzione multiuso , che può fare una mossa di registro-registro, un'aggiunta di registro-registro, compensare da una costante e ridimensionare il tutto in una singola istruzione!
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 byte)
L' ANDistruzione è come l' TESTistruzione che abbiamo usato in precedenza, in quanto entrambi eseguono un bit-AND e impostano i flag di conseguenza, ma in ANDrealtà aggiornano l'operando di destinazione. L' LEAistruzione quindi lo ridimensiona di 2, aggiunge il valore di input originale e decrementa di 1. Se il valore di input era dispari, questo sottrae 1 (2 × 0 - 1 = −1) da esso; se il valore di input era pari, questo aggiunge 1 (2 × 1 - 1 = 1) ad esso.
Questo è un modo molto veloce ed efficiente per scrivere il codice, poiché gran parte dell'esecuzione può essere eseguita nel front-end, ma non ci compra molto in termini di byte, dal momento che ci vogliono così tanti per codificare un complesso LEAistruzioni. Questa versione non funziona altrettanto bene per scopi di allineamento, poiché richiede che il valore di input originale sia preservato come input LEAdell'istruzione. Quindi, con questo ultimo tentativo di ottimizzazione, siamo effettivamente tornati indietro, suggerendo che potrebbe essere il momento di smettere.
Pertanto, per la voce finale concorrente, abbiamo una funzione a 9 byte che accetta il valore di input nel ECXregistro (una convenzione di chiamata semi-standard basata su registro su x86 a 32 bit) e restituisce il risultato nel EAXregistro (come con tutte le convenzioni di chiamata 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 per il montaggio con MASM; chiama da C come:
extern int __fastcall SwapParity(int value); // MSVC
extern int __attribute__((fastcall)) SwapParity(int value); // GNU