11
\$\begingroup\$

Introduction

During work with BMP (bitmap) generator I face problem of converting number to little endian hex string. Here is function which I create in JavaScript - but wonder how small code can works similarly

let liEnd= num => num.toString(16).padStart(8,'0').match(/../g).reverse().join``;
console.log(liEnd(304767)) // 304767 dec = 0x4a67f hex

Challenge

Write function which will take 32bit unsigned integer number on input, and produce 8-digit hexadecimal string with little endian order. The example algorithm which do the job:

  • convert numb to hex string e.g: 304767 -> '4a67f'
  • add padding zeros to get 8-char string: '0004a67f'
  • split string to four 2-char pieces: '00','04','a6','7f'
  • reverse order of pieces '7f','a6','04','00'
  • join pieces and return as result: '7fa60400'

Example Input and Output

Input number (or string with dec number) is on the left of ->, output hex string is on the right

2141586432 -> 0004a67f
304767     -> 7fa60400
\$\endgroup\$

24 Answers 24

7
\$\begingroup\$

05AB1E, 10 9 bytes

žJ+h¦2ôRJ

Try it online!

-1 byte by inspiration of the Jelly answer.

žJ+   add 2^32 to input
h     convert to hex
¦     drop leading 1
2ô    split in groups of 2
R     reverse groups
J     and join them
\$\endgroup\$
6
\$\begingroup\$

R, 54 53 bytes

format.hexmode(scan()%/%256^(0:3)%%256%*%256^(3:0),8)

Try it online!

Each group of 2 characters is actually the hex representation of a digit in base 256. scan()%/%256^(0:3)%%256 converts to a base 256 number with 4 digits reversed, ...%*%256^(3:0) joins them as a single integer, and format.hexmode(...,8) converts that number to its hex representation with 8 digits.

\$\endgroup\$
5
\$\begingroup\$

JavaScript (ES7),  59  57 bytes

String manipulation.

n=>(n+2**32).toString(16).match(/\B../g).reverse().join``

Try it online!

How?

We first convert \$n + 2^{32}\$ to hexadecimal to make sure that all leading \$0\$'s are included:

(304767 + 2**32).toString(16) // --> '10004a67f'

Try it online!

We use the regular expression /\B../g to match all groups of 2 digits, ignoring the leading \$1\$ thanks to \B (non-word boundary).

'10004a67f'.match(/\B../g) // --> [ '00', '04', 'a6', '7f' ]

Try it online!

We reverse() and join() to get the final string.


JavaScript (ES6), 61 bytes

Recursive function.

f=(n,k=4)=>k?[(x=n&255)>>4&&'']+x.toString(16)+f(n>>8,k-1):''

Try it online!

\$\endgroup\$
  • \$\begingroup\$ ⭐ - you get star for nice answer - I like it, short but still clean and "human-redable" :) \$\endgroup\$ – Kamil Kiełczewski Oct 2 at 5:57
5
\$\begingroup\$

Python 3, 37 bytes

lambda n:n.to_bytes(4,"little").hex()

Try it online!

Arithmetic-based recursive solution (50 49 bytes, works also for Python 2):

f=lambda n,i=4:i*'1'and"%02x"%(n%256)+f(n>>8,i-1)

Try it online!

-1 byte thanks to @JonathanAllan

\$\endgroup\$
  • \$\begingroup\$ I'd say submit the recursive one as a Python 2 entry :) \$\endgroup\$ – Jonathan Allan Oct 1 at 19:14
  • \$\begingroup\$ f=lambda n,i=4:i*'1'and'%02x'%(n%256)+f(n>>8,i-1) saves a byte :) \$\endgroup\$ – Jonathan Allan Oct 1 at 19:18
  • \$\begingroup\$ @JonathanAllan Thanks. I am not familiar with all Python 2 tricks and do not see how it can be made shorter though. \$\endgroup\$ – Joel Oct 1 at 19:21
  • \$\begingroup\$ it doesn't but the 37 won't work in py 2 \$\endgroup\$ – Jonathan Allan Oct 1 at 19:23
  • \$\begingroup\$ Yeah. Some of those built-ins are Python-3-only. \$\endgroup\$ – Joel Oct 1 at 19:28
5
\$\begingroup\$

Zsh, 46 bytes

i=$1
repeat 4 printf %02x $[j=i%256,i=i/256,j]

Try it online!

\$\endgroup\$
5
\$\begingroup\$

C# (Visual C# Interactive Compiler), 54 bytes

x=>$"{(x=x>>16|x<<16)>>8&16711935|(x&16711935)<<8:x8}"

Saved 4 bytes thanks to @PeterCordes

Try it online!

Explanation

x=>                                                    //Lambda taking in an uint
     (x=x>>16|x<<16)                                   //Swap the first two and the last two bytes of the uint (0x7fa60400 -> 0x04007fa6)
                    >>8&16711935|(x&16711935)<<8       //Swap each pair of bytes in every group of 2 bytes (0x04007fa6 -> 0x0004a67f)
  $"{                                           :x8}"  //Format as hex string, padded with leading zeroes to length 8
\$\endgroup\$
  • \$\begingroup\$ Can you shrink the 4278255360 mask constant to 16711935 (0xff00ff) if you shift before masking? Or does that cost extra parens? Also, if not then 0xff00ff00 is the same length but much more meaningful to humans. \$\endgroup\$ – Peter Cordes Oct 2 at 11:21
  • \$\begingroup\$ @PeterCordes It also has the added advantage of being able to remove the brackets as >> has higher precedence than &, whcih saved 4 bytes total. Thanks! \$\endgroup\$ – Embodiment of Ignorance Oct 3 at 2:29
  • \$\begingroup\$ Cool. In your "explanation" section, I'd suggest writing the constants in hex. \$\endgroup\$ – Peter Cordes Oct 3 at 3:05
4
\$\begingroup\$

Japt -P, 10 bytes

sG ùT8 ò w

Try it

sG ùT8 ò w     :Implicit input of integer
s              :Convert to string
 G             :  In base-16
   ù           :Left pad
    T          :  With 0
     8         :  To length 8
       ò       :Split into 2s
         w     :Reverse
               :Implicitly join and output
\$\endgroup\$
  • \$\begingroup\$ What does -P do? \$\endgroup\$ – JL2210 Oct 1 at 21:03
  • \$\begingroup\$ 🚀 your answer is in the top (can you add explanation?) \$\endgroup\$ – Kamil Kiełczewski Oct 2 at 6:03
  • \$\begingroup\$ @JL2210 From the docs: "-P: If the output is an array, outputs with no separator (i.e. joined with P).". So the flag is for an implicit instead of explicit join to save bytes. :) \$\endgroup\$ – Kevin Cruijssen Oct 2 at 8:08
  • 2
    \$\begingroup\$ @KamilKiełczewski, explanation added. \$\endgroup\$ – Shaggy Oct 2 at 8:21
4
\$\begingroup\$

C (gcc), 30 bytes

f(x){printf("%.8x",htonl(x));}

Try it online!

\$\endgroup\$
  • \$\begingroup\$ when run on a big-endian machine, this won't convert to little-endian? \$\endgroup\$ – peter ferrie Oct 4 at 19:41
  • \$\begingroup\$ @peterferrie See revision 3. \$\endgroup\$ – JL2210 Oct 4 at 20:05
4
\$\begingroup\$

Python 2, 43 bytes

lambda n:[("%08x"%n)[i^6]for i in range(8)]

Try it online!

-4 bytes thanks to benrg

Outputs a list of characters. Computed by retrieving, in order, the hex digits of the input at indices 6, 7, 4, 5, 2, 3, 0, 1.

\$\endgroup\$
  • 2
    \$\begingroup\$ [i^6]for i in range(8) saves a few bytes. \$\endgroup\$ – benrg Oct 2 at 20:22
  • \$\begingroup\$ Is it allowed to output list instead of string? \$\endgroup\$ – Qwertiy Oct 2 at 22:45
  • \$\begingroup\$ output as list is not really fit for the spirit of the question imo \$\endgroup\$ – qwr Oct 3 at 3:20
3
\$\begingroup\$

C (gcc) endian agnostic, no standard libs, 92 91 bytes

h(n) is a single-digit integer->hex helper function.
f(x,p) takes an integer and a char[8] pointer. The result is 8 bytes of char data. (Not 0-terminated unless the caller does that.)

Assumptions: ASCII character set. 2's complement int so right shift eventually brings down the sign bit, and converting a uint32_t to int doesn't munge the bit-pattern if the high bit is set. int is at least 32-bit. (Wider might let it work on 1's complement or sign-magnitude C implementations).

Non-assumptions: anything about implementation byte-order or signedness of char.

i;h(n){n&=15;return n>9?n+87:n+48;}f(x,p)char*p;{for(i=5;--i;x>>=8)*p++=h(x>>4),*p++=h(x);}

Try it online! including test caller using printf("%.8s\n", buf) to print output buffer without 0-terminating it.

Ungolfed:

int h(n){n&=15;return n>9 ? n+'a'-10 : n+'0';}      // single digit integer -> hex

int i;
void ungolfed_f(x,p)char*p;{
    for(i=5; --i; x>>=8)   // LS byte first across bytes
        *p++=h(x>>4),      // MS nibble first within bytes
        *p++=h(x);
}

Doing n&=15; inside h(x) is break-even; 6 bytes there vs. 3 each for &15 to isolate the low nibble at both call sites.

, is a sequence point (or equivalent in modern terminology) so it's safe to do *p++= stuff twice in one statement when separated by the , operator.

>> on signed integer is implementation-defined as either arithmetic or logical. GNU C defines it as arithmetic 2's complement. But on any 2's complement machine it doesn't really matter because we never look at the shifted-in 0s or copies of the sign bit. The original MSB will eventually get down into the low byte unchanged. This is not the case on sign/magnitude, and I'm not sure about 1's complement.

So this may only be portable to 2's complement C implementations. (Or where int is wider than 32 bits so bit 31 is just part of the magnitude.) unsigned -> signed conversion also munges the bit-pattern for negative integers, so &15 on an int would only extract nibbles of the original unsigned value on 2's complement. Again, unless int was wider than 32-bit so all inputs are non-negative.

The golfed version has UB from falling off the end of a non-void function. Not to return a value, just to avoid declaring it void instead of default int. Modern compilers will break this with optimization enabled.


Motivation: I was considering an x86 or ARM Thumb asm answer, thought it might be fun to do it manually in C, maybe for compiler-generated asm as a starting point. See https://stackoverflow.com/questions/53823756/how-to-convert-a-number-to-hex for speed-efficient x86 asm, including an AVX512VBMI version that's only 2 instructions (but needs control vectors for vpmultishiftqb and vpshufb so wouldn't be great for golf). Normally it takes extra work for SIMD to byte-reverse into printing order on little-endian x86 so this byte-reversed hex output is actually easier than normal.


Other ideas

I considered taking the integer by reference and looping over its bytes with char*, on a little-endian C implementation (like x86 or ARM). But I don't think that would have saved much.

Using sprintf to do 1 byte at a time, 64 bytes after golfing:

int i;
void f(x,p)char*p;{
        for(i=4;sprintf(p,"%.2x",x&255),--i;x>>=8)
                p+=2;
}

But if we're using printf-like functions we might as well byte-swap and do a %x printf of the whole thing like @JL2210's answer.

\$\endgroup\$
  • \$\begingroup\$ ⭐ - you get star for nice answer \$\endgroup\$ – Kamil Kiełczewski Oct 2 at 11:15
3
\$\begingroup\$

x86 SIMD machine code (AVX512-VBMI), 36 bytes

(16 bytes of which are a hex lookup table)

This is a function that takes an integer in xmm0 and returns 8 bytes of ASCII char data in xmm0, for the caller to store wherever it wants. (e.g. to video memory after interleaving with attribute bytes, or into a string under construction, or whatever)

From C, call it as __m128i retval = lehex(_mm_cvtsi32_si128(x)) with the x86-64 System V calling convention, or MS Windows vectorcall.

# disassembly with machine-code bytes (the answer) and NASM source code.
0000000000401000 <lehex>:
  401000:       c5 f1 72 d0 04          vpsrld      xmm1, xmm0, 4         ; AVX1
  401005:       c5 f1 60 c8             vpunpcklbw  xmm1, xmm1, xmm0      ; AVX1
  401009:    62 f2 75 08 8d 05 01 00 00 00 vpermb  xmm0, xmm1, [rel .hex_lut]
  401013:       c3                      ret    

0000000000401014 <lehex.hex_lut>:
  401014:     30 31 ...  61 62 ...     .hex_lut:  db "0123456789abcdef"

Total = 0x24 = 36 bytes.

See How to convert a number to hex? on SO for how this works. (SSE2 for the shift / punpck, then vpermb saves work that we'd need for pshufb. AVX1 instead of SSE2/SSSE3 also avoids a movaps register copy.)

Notice that punpcklbw with the source operands in that order will give us the most-significant nibble of the low input byte in the lowest byte element, then the least-significant nibble of the lowest source byte. (In that SO answer, a bswap is used on the input to get a result in standard printing order with only SSE2. But here we want that order: high nibble in lower element within each byte, but still little-endian byte order).

If we had more data constants, we could save addressing-mode space by doing one mov edx, imm32 then using [rdx+16] or whatever addressing modes. Or vpbroadcastb xmm0, [rdx+1].

But I think a 16-byte hex LUT + vpermb is still better than implementing the n>9 : n+'a'-10 : n+'0' condition: that requires 3 constants and at least 3 instructions with AVX512BW byte-masking (compare into mask, vpaddb, merge-masked vpaddb), or more with AVX1 or SSE2. (See How to convert a number to hex? on SO for an SSE2 version of that). And each AVX512BW instruction is at least 6 bytes long (4-byte EVEX + opcode + modrm), longer with a displacement in the addressing mode.

Actually it would take at least 4 instructions because we need to clear high garbage with andps, (or EVEX vpandd with a 4-byte broadcast memory operand) before the compare. And each of those needs a different vector constant. AVX512 has broadcast memory operands, but only for elements of 32-bit and wider. e.g. EVEX vpaddb's last operand is only xmm3/m128, not xmm3/m128/m8bcst. (Intel's load ports can only do 32 and 64-bit broadcasts for free as part of a load uop so Intel designed AVX512BW to reflect that and not be able to encode byte or word broadcast memory operands at all, instead of giving them the option to do dword broadcasts so you can still compress your constants to 4 bytes :/.)

The reason I used AVX512VBMI vpermb instead of SSSE3 / AVX1 pshufb is twofold:

  • vpermb ignores high bits of the selectors. (v)pshufb zeros bytes according to the high bit of the control vector and would have needed an extra pand or andps to actually isolate nibbles. With XMM / 16-byte size, vpermb only looks at the low 4 bits of the shuffle-control elements, i.e. bits [3:0] in Intel's notation in the Operation section.
  • vpermb can take the data to be shuffled (the lookup table) as a memory operand. (v)pshufb's xmm/mem operand is the shuffle-control vector.

Note that AVX512VBMI is only available on CannonLake / Ice Lake so you probably need a simulator to test this, like Intel's SDE.

\$\endgroup\$
  • \$\begingroup\$ ⭐ - you get star for nice answer \$\endgroup\$ – Kamil Kiełczewski Oct 2 at 11:13
  • \$\begingroup\$ @KamilKiełczewski: lol thanks. Converting numbers to hex efficiently is one of my favourite things. It's a nice use-case for several neat tricks and bit-manipulation. \$\endgroup\$ – Peter Cordes Oct 2 at 11:15
3
\$\begingroup\$

Scala, 58 40 36 bytes

"%08X"format Integer.reverseBytes(_)

Try it online!

Still uses the builtin to reverse the bytes of an Int, but uses format to format the Int as a Hex. No need to call toHexString.

Removed the parens on format. This now means that the argument can be taken implicitly using _.

\$\endgroup\$
2
\$\begingroup\$

Forth (gforth), 52 51 40 bytes

: f hex 0 4. do <# # # 0. #> type loop ;

Try it online!

Code explanation

: f           \ start a new word definition
  hex         \ set the current base to base 16
  0           \ convert the input number to a double-cell integer
  4. do       \ start a counted loop from 0 to 3
    <# # #    \ start a formatted numeric string and move last 2 digits to format area
    0.        \ move remaining digits down the stack
    #>        \ delete top two stack value and convert format area to string
    type      \ output string
  loop        \ end loop
;             \ end word definition
\$\endgroup\$
2
\$\begingroup\$

Jelly, 13 bytes

+Ø%b⁴Ḋs2Ṛ‘ịØh

Try it online!

A full program that takes an integer as its argument and prints a string.

\$\endgroup\$
  • \$\begingroup\$ 🚀 your answer is in the top \$\endgroup\$ – Kamil Kiełczewski Oct 2 at 19:26
2
\$\begingroup\$

APL+WIN, 36 34 bytes

2 bytes saved by converting to index zero

Prompts for integer:

'0123456789abcdef'[,⊖4 2⍴(8⍴16)⊤⎕]

Try it online! Courtesy Dyalog Classic

\$\endgroup\$
2
\$\begingroup\$

Excel, 91 bytes

=RIGHT(DEC2HEX(A1,8),2)&MID(DEC2HEX(A1,8),5,2)&MID(DEC2HEX(A1,8),3,2)&LEFT(DEC2HEX(A1,8),2)
\$\endgroup\$
1
\$\begingroup\$

Charcoal, 11 bytes

⪫⮌⪪﹪%08xN²ω

Try it online! Link is to verbose version of code. Explanation:

        N   Input as a number
   ﹪%08x    Format using literal string
  ⪪      ²  Split into pairs of characters
 ⮌          Reverse
⪫         ω Join
            Implicitly print

19 bytes without resorting to Python formatting:

⪫…⮌⪪⍘⁺X²¦³⁶N¹⁶¦²¦⁴ω

Try it online! Link is to verbose version of code. Explanation:

           N        Input as a number
     ⁺              Plus
       ²            Literal 2
      X             To power
         ³⁶         Literal 36
    ⍘               Convert to base
            ¹⁶      Literal 16
   ⪪           ²    Split into pairs of digits
  ⮌                 Reverse the list
 …               ⁴  Take the first 4 pairs
⪫                 ω Join together
                    Implicitly print
\$\endgroup\$
  • \$\begingroup\$ 🚀 your answer is in the top \$\endgroup\$ – Kamil Kiełczewski Oct 2 at 9:50
1
\$\begingroup\$

K4, 12 11 bytes

Solution:

,/$|4_0x0\:

Examples:

q)k),/$|4_0x0\:304767
"7fa60400"
q)0W
"0004a67f"

Explanation:

Pretty much exactly what the question asks:

,/$|4_0x0\: / the solution
      0x0\: / split to bytes
    4_      / drop first 4 bytes
   |        / reverse
  $         / convert to string
,/          / flatten

Notes:

  • -1 byte as K4 numbers are longs (64bit) by default, so dropping 4 bytes (32bits)
\$\endgroup\$
  • \$\begingroup\$ 🚀 your answer is in the top \$\endgroup\$ – Kamil Kiełczewski Oct 2 at 11:08
1
\$\begingroup\$

Perl 5 (-p), 22 bytes

$_=unpack H8,pack V,$_

Try it online!

\$\endgroup\$
1
\$\begingroup\$

PHP, 31 bytes

<?=unpack(H8,pack(V,$argn))[1];

Try it online!

Taking advantage of PHP's pack and unpack, I pack the unsigned input with "32 bit little endian byte order" format (V) into a binary string and then unpack it with "hex string, high nibble first" format (H) and print the result.

This seems to be one of the rare cases where PHP's built-ins are actually shorter than implementing a simple algorithm!

\$\endgroup\$
1
\$\begingroup\$

J, 10 bytes

8{._1{3!:3

Try it online!

how

3!:3 is a J "foreign conjunction" for hex representation, documented here. That is, it's a builtin for converting to hex. However, it's output it not quite what we want. Eg, running:

3!:3 (304767)

produces:

e300000000000000
0400000000000000
0100000000000000
0000000000000000
7fa6040000000000

The meaning of the other lines is explained on the doc page I linked to above. In any case, it's clear we want the first 8 chars of the last line.

_1{ get the last line.

8{. gets the first 8 characters of it.

\$\endgroup\$
  • \$\begingroup\$ 🚀 your answer is in the top \$\endgroup\$ – Kamil Kiełczewski Oct 2 at 6:02
1
\$\begingroup\$

Ruby, 31 27 bytes

Ended up being a port of Night2's PHP answer because Ruby has the same pack/unpack functionality.

->*i{i.pack(?V).unpack'H8'}

Try it online!

My original 31-byte answer that didn't take advantage of the H8 unpack mode because I didn't know about it:

->*i{'%02x'*4%i.pack(?V).bytes}

Try it online!

\$\endgroup\$
1
\$\begingroup\$

Windows Batch, 90 bytes

@for /l %%x in (24,-8,0)do @set/aa=%1^>^>%%x^&255&cmd/cexit !a!&<nul set/p=!=exitcode:~-2!

Run the command-line with /v to enable the delayed expansion.

\$\endgroup\$
1
\$\begingroup\$

x86 32-bit machine code, 24 21 bytes

changelog: -3 bytes: replace standard add/cmp/jbe/add with a DAS hack by @peter ferrie

64-bit: still 24 bytes. Long mode removed the DAS opcode.
16-bit mode: the default operand-size is 16-bit but the problem spec is inherently 32-bit. Including hard-coded 8 hex digits.


Byte-reverse with bswap then manual int->hex in standard order (most-significant nibble first, writing hex digits to a char output buffer in ascending order.) This avoids needing to unroll the loop to switch order between nibbles within a byte vs. across bytes.

Callable as void lehex(char buf[8] /*edi*/, uint32_t x /*esi*/); like x86-64 System V, except this doesn't work in 64-bit mode. (It needs the output pointer in EDI for stosb. The input number can be in any register other than ECX or EAX.)

     1                             lehex:
     2 00000000 0FCE                   bswap  esi
     3 00000002 6A08                   push   8            ; 8 hex digits
     4 00000004 59                     pop    ecx
     5                             .loop:                ;do{
     6 00000005 C1C604                 rol    esi, 4       ; rotate high nibble to the bottom
     7                             
     8 00000008 89F0                   mov    eax, esi
     9 0000000A 240F                   and    al, 0x0f     ; isolate low nibble
    10 0000000C 3C0A                   cmp al, 10          ; set CF according to digit <= 9
    11 0000000E 1C69                   sbb al, 0x69        ; read CF, set CF and conditionally set AF
    12 00000010 2F                     das                 ; magic, which happens to work
    13                             
    14 00000011 AA                     stosb               ; *edi++ = al
    15 00000012 E2F1                   loop  .loop       ; }while(--ecx)
    16                             
    17 00000014 C3                     ret

size = 0x15 = 21 bytes.

TIO FASM 32-bit x86 test case with an asm caller that uses a write system call to write the output after calling it twice to append 2 strings into a buffer. Tests all hex digits 0..F, including 9 and A at the boundary between numeral vs. letter.

The DAS hack - x86 has a half-carry flag, for carry out of the low nibble. Useful for packed-BCD stuff like the DAS instruction, intended for use after subtracting two 2-digit BCD integers. With the low nibble of AL being outside the 0-9 range, we're definitely abusing it here.

Notice the if (old_AL > 99H) or (old_CF = 1) THEN AL ← AL − 60H; part of the Operation section in the manual; sbb always sets CF here so that part always happens. That and the ASCII range for upper-case letters is what motivates the choice of sub al, 0x69

  • cmp 0xD, 0xA doesn't set CF
  • sbb 0xD - 0x69 wraps to AL=0xA4 as input to DAS. (And sets CF, clears AF)
  • no AL -= 6 in the first part of DAS (because 4 > 9 is false and AF=0)
  • AL -= 0x60 in the second part, leaving 0x44, the ASCII code for 'D'

vs. a numeral:

  • cmp 0x3, 0xA sets CF
  • sbb 3 - 0x69 - 1 = AL = 0x99 and sets CF and AF
  • no AL -= 6 in the first part of DAS (9 > 9 is false but AF is set), leaving 0x93
  • AL -= 0x60 in the second part, leaving 0x33, the ASCII code for '3'.

Subtracting 0x6a in SBB will set AF for every digit <= 9 so all the numerals follow the same logic. And leave it cleared for every alphabetic hex digit. i.e. correctly exploiting the 9 / A split handling of DAS.


Normally (for performance) you'd use a lookup table for a scalar loop, or possibly a branchless 2x lea and cmp/cmov conditional add. But 2-byte al, imm8 instructions are a big win for code-size.


x86-64 version version: just the part that's different, between and al, 0xf and stosb.

;; x86-64 int -> hex  in 8 bytes
    10 0000000C 0430                   add    al, '0'
    11 0000000E 3C39                   cmp    al, '9'
    12 00000010 7602                   jbe  .digit
    13 00000012 0427                     add    al, 'a'-10 - '0'     ; al =  al>9 ? al+'a'-10 : al+'0'
    14                             .digit:

Notice that the add al, '0' always runs, and the conditional add only adds the difference between 'a'-10 and '0', to make it just an if instead of if/else.

Tested and works, using the same main caller as my C answer, which uses char buf[8] and printf("%.8s\n", buf).

\$\endgroup\$
  • \$\begingroup\$ can you create online-working snippet in e.g. here ? \$\endgroup\$ – Kamil Kiełczewski Oct 2 at 10:08
  • \$\begingroup\$ @KamilKiełczewski: TIO makes it impossible (AFAIK) to write the caller in C to test an asm function so I often don't bother, but sure since you asked and sys_write can output fixed-length strings easily. Oh interesting, I hadn't realized FASM on TIO let you create 32-bit executables, unlike with NASM where it doesn't respect -felf32. I prefer x86-64 anyway, and this answer doesn't save any bytes from 32-bit code. \$\endgroup\$ – Peter Cordes Oct 2 at 10:41
  • \$\begingroup\$ ⭐ - you get star for nice answer \$\endgroup\$ – Kamil Kiełczewski Oct 2 at 11:14
  • 1
    \$\begingroup\$ @JL2210: Do you mean sprintf? I don't think libc has any handy int->string functions other than format-string based ones, only string->int like strtoul. But yes, bswap / printf would probably be shorter, if you can figure out some way to count bytes for the GOT entry for a function in a dynamic library (besides the 6-byte call [rel printf wrt ..got] call site); a minimal statically linked executables can be significantly smaller than dynamic, at least when made by ld with normal defaults. But I don't think it would be reasonable to statically link it but not count its code size. \$\endgroup\$ – Peter Cordes Oct 2 at 16:19
  • 1
    \$\begingroup\$ @JL2210: Remember, this is an x86 machine code answer, not asm text source size. I haven't used libc functions in previous machine-code answers, only Linux system calls (e.g. in Fibonacci), and IDK how I'd go about counting the cost or whether I even want to write machine-code-with-libc answers. There are use-cases for x86 machine code where a libc isn't available, e.g. in a bootloader. \$\endgroup\$ – Peter Cordes Oct 2 at 16:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.