g | x | w | all
Bytes Lang Time Link
014Pip251014T205932ZDLosc
053JavaScript Node.js251014T184440Zl4m2
034Python 3.11+230419T142544ZShadowRa
021x86 32bit machine code191002T100035ZPeter Co
037Red210601T174713Z9214
034Rust210601T195400ZAiden4
090Windows Batch191004T193836Zpeter fe
027Ruby191003T224149ZValue In
043Python 2191002T131858Znegative
010J191001T231543ZJonah
031PHP191003T130351ZNight2
022Perl 5 p191003T131500ZGrimmy
011K4191002T110124Zmkst
054C# Visual C# Interactive Compiler191001T202033ZGymhgy
030C gcc191001T190304ZS.S. Ann
036Scala191002T092733ZSoapy
036x86 SIMD machine code AVX512VBMI191002T092724ZPeter Co
011Charcoal191002T094649ZNeil
091Excel191002T092820ZWernisch
010Japt P191001T191545ZShaggy
091C gcc endian agnostic191002T074200ZPeter Co
00905AB1E191001T184018ZDorian
046Zsh191001T232751ZGammaFun
053R191001T183823ZRobin Ry
034APL+WIN191001T202454ZGraham
013Jelly191001T203925ZNick Ken
040Forth gforth191001T183700Zreffu
037Python 3191001T190326ZJoel
057JavaScript ES7191001T172427ZArnauld

Pip, 14 bytes

Sa+E32TB16<>-2

Attempt This Online!

Explanation

Sa+E32TB16<>-2
 a              Command-line argument
  +E32          Add 2^32 for padding
      TB16      Convert to base 16
S               Remove the leading 1 from the 2^32 addition
          <>-2  Break into groups of 2 from right to left
                Concatenate and autoprint (implicit)

JavaScript (Node.js), 53 bytes

f=(n,i=8)=>i--?(n>>4*(i^6)&15).toString(16)+f(n,i):''

Try it online!

JavaScript (Node.js), 55 bytes

n=>'10325476'.replace(/./g,t=>(n>>4*t&15).toString(16))

Try it online!

Python 3.11+, 34 bytes

lambda n:n.to_bytes(4)[::-1].hex()

This is a version of Joel's Python 3 solution that works only on Python 3.11 and higher. Pre-3.11, the byteorder argument to int.to_bytes/int.from_bytes was mandatory; in 3.11 it is now defaulted to 'big', so, golf-wise, you can omit it, let to_bytes produce the big-endian version, then reverse it with a slice, saving three characters.

Attempt This Online!

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 sbb al, 0x69.

vs. a numeral:

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.

'A' is 0x41, '9' is 0x39. So 0xa + '0' needs an extra 7 added to get from '9'+1 to 'A'. DAS involves a -=6 or -=0x60, hence the use of SBB to subtract an extra 1 for one of the cases.


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).

Red, 37 bytes

func[x][reverse/skip form to-hex x 2]

This breaks TIO because of the outdated compiler it uses.

Red, 43 bytes

func[x][load next mold reverse to-binary x]

Try it online!

Rust, 34 bytes

|n|print!("{:08x}",n.swap_bytes())

Try it online!

Rust has built-in hex formatting, but it displays in big-endian format, so I reversed the byte order first. Capitalize the x if you want uppercase hex digits instead of lowercase ones.

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.

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!

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.

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.

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!

Perl 5 (-p), 22 bytes

$_=unpack H8,pack V,$_

Try it online!

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:

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

C (gcc), 30 bytes

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

Try it online!

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 _.

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:

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

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

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)

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

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.

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

Zsh, 46 bytes

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

Try it online!

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.

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

Jelly, 13 bytes

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

Try it online!

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

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

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

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!