x86+MMX machine code, 10 bytes
Despite the question only saying the input has to be "binary", comments seem to imply they're thinking of a text string of base-2 digits, not 1-bit digits packed into an integer which a computer can already use directly as an unsigned integer. If that's allowed, see my 7-byte answer on Output / Convert to unary number.
This is that answer plus conversion from 8 bytes to an 8-bit integer, by extracting the high bit of each input. Input is in mm0
, an MMX vector register, with the lowest element being the least significant. (If stored to memory, this would be opposite of standard printing order where the most significant goes first, at the lowest address of a string.)
; machine code | NASM source
; RDI or ES:EDI = output buffer of size n+1
binary_to_unary:
; pslld mm0, 7 ; 4 bytes. For input digits '0' / '1', shift the low bit to high
0FD7C8 pmovmskb ecx, mm0 ; pack the binary digits into an integer in ECX
; memset(dst, '1', n)
B031 mov al, '1'
F3AA rep stosb
880F mov byte [rdi], cl ; terminate the C-style string
C3 ret
For ASCII '0'
/ '1'
input, we'd start with pslld mm0, 7
to shift the low bit to the high position of each byte. Also, if the digit-string was loaded from a string in memory in printing order (most significant at lowest address), we'd need to byte-reverse. In that case scalar code to read input would be smaller than movbe
or load+bswap
in an integer register + movq mm0, rax
, or psufb
with an 8-byte vector constant.
In this current version, our input string in an MMX register is effectively big-endian, most significant digit in the highest element number (on the left when writing it in the notation Intel manuals use to document vector instructions).
x86 scalar machine code, 14 bytes, input from a C string in printing order
- RDI or ES:EDI points to an input buffer of base-2 ASCII digits, terminated by a
0
byte (or any byte below ASCII '0'
), padded to at least 32 digits.
- RSI or ES:ESI points to an output buffer of size n+1, will be filled with
'0'
digits and terminated with a 0
.
Callable from C with the x86-64 System V calling convention as void binary_string_to_unary(const char *binary_src, char *unary_dst);
. Use the x32 ABI for 32-bit pointers, or change the mov edi, esi
to push rsi
/pop rdi
to copy a 64-bit register with the same code size. (Or use the same binary machine code in 32-bit mode.)
binary_string_to_unary:
B030 mov al, '0' ; input digit to compare against, also the output digit
.loop:
11C9 adc ecx, ecx ; ECX = 2*ECX + CF
AE scasb ; set FLAGS like cmp al, [rdi] then increment RDI. CF=1 for digit='1', CF=0 for digit='0'.
76FB jbe .loop ; while('0' <= digit); i.e. while digit >= '0', false for digit = 0 terminator
89F7 mov edi, esi ; x32 ABI, or for 32-bit mode. For full 64-bit pointers, push rsi / pop rdi
F3AA rep stosb ; memset(rdi, al='0', rcx)
880F mov [rdi], cl ; terminate the C-style string
C3 ret
Note that we don't xor ecx,ecx
before the loop. Instead we rely on the input string being '0'-padded to at least 32 digits, so the previous contents get shifted out.
There might be a way (with same code size) to take the input pointer in RSI like you'd expect from the traditional meanings of those registers, perhaps with lodsb
/ cmp al, '0'
(3 bytes instead of 1 for scasb, but avoiding the 2-byte mov edi, esi
after the loop). Change the branch conditions accordingly for comparing in the other direction. Except that would get CF=0 for digit='1'. So maybe add al, 255-'0'
so '1' produces a carry-out but '0' doesn't? And the terminator is a value that results in some FLAGS condition that the other two don't, perhaps a signed overflow and/or the sign flag.
any reasonable input & output format
I'm guessing taking the input as an integer is not acceptable, but it would be good to state it explicitly. \$\endgroup\$unsigned
int); that's a good way to store binary! \$\endgroup\$