Integer to hex string in C++
Asked Answered
W

28

215

How do I convert an integer to a hex string in C++?

I can find some ways to do it, but they mostly seem targeted towards C. It doesn't seem there's a native way to do it in C++. It is a pretty simple problem though; I've got an int which I'd like to convert to a hex string for later printing.

Wiatt answered 24/2, 2011 at 5:26 Comment(0)
C
325

Use <iomanip>'s std::hex. If you print, just send it to std::cout, if not, then use std::stringstream

std::stringstream stream;
stream << std::hex << your_int;
std::string result( stream.str() );

You can prepend the first << with << "0x" or whatever you like if you wish.

Other manips of interest are std::oct (octal) and std::dec (back to decimal).

One problem you may encounter is the fact that this produces the exact amount of digits needed to represent it. You may use setfill and setw this to circumvent the problem:

stream << std::setfill ('0') << std::setw(sizeof(your_type)*2) 
       << std::hex << your_int;

So finally, I'd suggest such a function:

template< typename T >
std::string int_to_hex( T i )
{
  std::stringstream stream;
  stream << "0x" 
         << std::setfill ('0') << std::setw(sizeof(T)*2) 
         << std::hex << i;
  return stream.str();
}
Clement answered 24/2, 2011 at 5:30 Comment(13)
sizeof(your_type)*2 is a bit ugly; numeric_limits<your_type>::digits/4 is cleaner.Magallanes
@Magallanes - quite on the contrary. Test your suggestion on the int type ;)Clement
@Kornel, great answer, but why template it? To support long int?Slam
@LexFridman, to emit exactly the amount of hex digits as needed. Why emit 8 digits if the type is a uint8_t?Clement
std::showbase will show a 0x prefix...Status
WARNING: this will not work for single byte because char is always threated as charStealth
You also require #include <sstream>Eldoraeldorado
If I am formatting multiple ints, It seems that std::setw needs to be output to the stream for every int, whereas std::hex, std::setfill, std::uppercase, ... only need to be sent to the output stream once. This seems inconsistent?Scion
I expect this will annoy everyone else in your project since so few people ever think to << std:dec before pushing numbers into a stream. I thought was a widely known flaw in C++ streams?Callous
If you are writing directly to a shared stream like std::coutyou may want to save and restore the stream flags around your modifications, so you leave the stream with it's previous set flags after you've been tampering with them.. Something like const auto save_flags = std::cout.flags(); std::cout << std::hex << 42 << std::endl; std::cout.flags(save_flags); std::cout << 42 << std::endl;Farnesol
error: no match for ‘operator<<’ (operand types are ‘std::stringstream’ {aka ‘std::__cxx11::basic_stringstream<char>’} and ‘const char [3]’), where the const char [3] is "0x".Paresh
UPD: fix is: #include <sstream>.Paresh
While it was OK at the time of the OP question, this answer is now outdated. Accepted answers should be allowed to be deprecated and reviewed with time.Permissible
Z
83

You can do it with C++20 std::format:

std::string s = std::format("{:x}", 42); // s == 2a

Until std::format is widely available you can use the {fmt} library, std::format is based on (godbolt):

std::string s = fmt::format("{:x}", 42); // s == 2a

Disclaimer: I'm the author of {fmt} and C++20 std::format.

Zita answered 25/9, 2021 at 13:59 Comment(2)
how do we add padding and/or the correct 0x (hex) or 0o (octal) prefix for this? say for example I want 0x002a or 0o52?Higherup
Padding, prefix, etc., are syntax options for the format specifier, described here: fmt.dev/latest/syntax.html. For 0x002a, you'd use "{:#06x}"; for 0o52, you'd use "0o{:o}". The syntax is quite rich and flexible.Majordomo
U
60

To make it lighter and faster I suggest to use direct filling of a string.

template <typename I> std::string n2hexstr(I w, size_t hex_len = sizeof(I)<<1) {
    static const char* digits = "0123456789ABCDEF";
    std::string rc(hex_len,'0');
    for (size_t i=0, j=(hex_len-1)*4 ; i<hex_len; ++i,j-=4)
        rc[i] = digits[(w>>j) & 0x0f];
    return rc;
}
Unify answered 31/10, 2015 at 2:54 Comment(6)
Will this work for any type? I mean also double, float, uint8_t?Negrophobe
@Negrophobe It works for integral types, not for double and float (and not for pointers)Acciaccatura
... a very pragmatic (but valid) mix of C and C++, I'm not sure about speed ... for my taste, it's a bit dense.Acciaccatura
Thank you, brilliant answer. Bringing in a stream library 'just' to do this seems such a waste.Countable
This prints 0000FFFF for 0xFFFF. I prefer to have 0xFFFF as output.Smoothspoken
@DrumM pass a 4 into the hex_len parameter then?Joaquin
M
34

Use std::stringstream to convert integers into strings and its special manipulators to set the base. For example like that:

std::stringstream sstream;
sstream << std::hex << my_integer;
std::string result = sstream.str();
Meliorate answered 24/2, 2011 at 5:32 Comment(0)
T
20

Just print it as an hexadecimal number:

int i = /* ... */;
std::cout << std::hex << i;
Treulich answered 24/2, 2011 at 5:30 Comment(1)
I would use std::cout<<std::hex<<i<<std::dec;, otherwise all integers that are streamed out later will be in hex. You don't need to do that for the other answers that use stringstream because the stream is discarded after it is used, but cout lives forever.Finitude
W
14

Since C++20, with std::format, you might do:

std::format("{:#x}", your_int);    // 0x2a
std::format("{:#010x}", your_int); // 0x0000002a

Demo

Wriggle answered 5/8, 2021 at 11:5 Comment(0)
S
11
#include <boost/format.hpp>
...
cout << (boost::format("%x") % 1234).str();  // output is: 4d2
Subversion answered 20/6, 2018 at 7:1 Comment(2)
The header file is #include <boost/format.hpp>Dissentient
Boost doesn't come pre-installed that's whyBib
C
8

You can try the following. It's working...

#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
using namespace std;

template <class T>
string to_string(T t, ios_base & (*f)(ios_base&))
{
  ostringstream oss;
  oss << f << t;
  return oss.str();
}

int main ()
{
  cout<<to_string<long>(123456, hex)<<endl;
  system("PAUSE");
  return 0;
}
Clop answered 25/6, 2012 at 12:19 Comment(2)
a nice answer, but beware that to_string is part of the namespace std in C++11Demotic
@Berke yes, it is 2014 after all... heaven forbid we'll have to start dealing with C++14 soon.Berke
C
8

A new C++17 way: std::to_chars from <charconv> (https://en.cppreference.com/w/cpp/utility/to_chars):

char addressStr[20] = { 0 };
std::to_chars(std::begin(addressStr), std::end(addressStr), address, 16);
return std::string{addressStr};

This is a bit verbose since std::to_chars works with a pre-allocated buffer to avoid dynamic allocations, but this also lets you optimize the code since allocations get very expensive if this is in a hot spot.

For extra optimization, you can omit pre-initializing the buffer and check the return value of to_chars to check for errors and get the length of the data written. Note: to_chars does NOT write a null-terminator!

Candis answered 5/5, 2022 at 19:26 Comment(0)
A
6

Just have a look on my solution,[1] that I verbatim[2] copied from my project. My goal was to combine flexibility and safety within my actual needs:[3]

  • no 0x prefix added: caller may decide
  • automatic width deduction: less typing
  • explicit width control: widening for formatting, (lossless) shrinking to save space
  • capable for dealing with long long
  • restricted to integral types: avoid surprises by silent conversions
  • ease of understanding
  • no hard-coded limit
#include <string>
#include <sstream>
#include <iomanip>

/// Convert integer value `val` to text in hexadecimal format.
/// The minimum width is padded with leading zeros; if not 
/// specified, this `width` is derived from the type of the 
/// argument. Function suitable from char to long long.
/// Pointers, floating point values, etc. are not supported; 
/// passing them will result in an (intentional!) compiler error.
/// Basics from: https://mcmap.net/q/125608/-integer-to-hex-string-in-c
template <typename T>
inline std::string int_to_hex(T val, size_t width=sizeof(T)*2)
{
    std::stringstream ss;
    ss << std::setfill('0') << std::setw(width) << std::hex << (val|0);
    return ss.str();
}

[1] based on the answer by Kornel Kisielewicz
[2] Only the German API doc was translated to English.
[3] Translated into the language of CppTest, this is how it reads:

TEST_ASSERT(int_to_hex(char(0x12)) == "12");
TEST_ASSERT(int_to_hex(short(0x1234)) == "1234");
TEST_ASSERT(int_to_hex(long(0x12345678)) == "12345678");
TEST_ASSERT(int_to_hex((long long)(0x123456789abcdef0)) == "123456789abcdef0");
TEST_ASSERT(int_to_hex(0x123, 1) == "123");
TEST_ASSERT(int_to_hex(0x123, 8) == "00000123");
// width deduction test as suggested by Lightness Races in Orbit:
TEST_ASSERT(int_to_hex(short(0x12)) == "0012");
Acciaccatura answered 6/2, 2018 at 12:24 Comment(1)
Could show off the width deduction with e.g. TEST_ASSERT(int_to_hex(short(0x12)) == "0012");Fictive
C
6

I can see all the elaborate coding samples others have used as answers, but there is nothing wrong with simply having this in a C++ application:

    printf ("%04x", num);

for num = 128:

    007f

https://en.wikipedia.org/wiki/Printf_format_string

C++ is effectively the original C language which has been extended, so anything in C is also perfectly valid C++.

Cedric answered 21/3, 2022 at 7:7 Comment(2)
That's going to print to the console, but how can it be used as a string?Josiejosler
@GeneralGrievance using snprintf() instead.Precambrian
O
5

Thanks to Lincoln's comment below, I've changed this answer.

The following answer properly handles 8-bit ints at compile time. It doees, however, require C++17. If you don't have C++17, you'll have to do something else (e.g. provide overloads of this function, one for uint8_t and one for int8_t, or use something besides "if constexpr", maybe enable_if).

template< typename T >
std::string int_to_hex( T i )
{
    // Ensure this function is called with a template parameter that makes sense. Note: static_assert is only available in C++11 and higher.
    static_assert(std::is_integral<T>::value, "Template argument 'T' must be a fundamental integer type (e.g. int, short, etc..).");

    std::stringstream stream;
    stream << "0x" << std::setfill ('0') << std::setw(sizeof(T)*2) << std::hex;

    // If T is an 8-bit integer type (e.g. uint8_t or int8_t) it will be 
    // treated as an ASCII code, giving the wrong result. So we use C++17's
    // "if constexpr" to have the compiler decides at compile-time if it's 
    // converting an 8-bit int or not.
    if constexpr (std::is_same_v<std::uint8_t, T>)
    {        
        // Unsigned 8-bit unsigned int type. Cast to int (thanks Lincoln) to 
        // avoid ASCII code interpretation of the int. The number of hex digits 
        // in the  returned string will still be two, which is correct for 8 bits, 
        // because of the 'sizeof(T)' above.
        stream << static_cast<int>(i);
    }        
    else if (std::is_same_v<std::int8_t, T>)
    {
        // For 8-bit signed int, same as above, except we must first cast to unsigned 
        // int, because values above 127d (0x7f) in the int will cause further issues.
        // if we cast directly to int.
        stream << static_cast<int>(static_cast<uint8_t>(i));
    }
    else
    {
        // No cast needed for ints wider than 8 bits.
        stream << i;
    }

    return stream.str();
}

Original answer that doesn't handle 8-bit ints correctly as I thought it did:

Kornel Kisielewicz's answer is great. But a slight addition helps catch cases where you're calling this function with template arguments that don't make sense (e.g. float) or that would result in messy compiler errors (e.g. user-defined type).

template< typename T >
std::string int_to_hex( T i )
{
  // Ensure this function is called with a template parameter that makes sense. Note: static_assert is only available in C++11 and higher.
  static_assert(std::is_integral<T>::value, "Template argument 'T' must be a fundamental integer type (e.g. int, short, etc..).");

  std::stringstream stream;
  stream << "0x" 
         << std::setfill ('0') << std::setw(sizeof(T)*2) 
         << std::hex << i;

         // Optional: replace above line with this to handle 8-bit integers.
         // << std::hex << std::to_string(i);

  return stream.str();
}

I've edited this to add a call to std::to_string because 8-bit integer types (e.g. std::uint8_t values passed) to std::stringstream are treated as char, which doesn't give you the result you want. Passing such integers to std::to_string handles them correctly and doesn't hurt things when using other, larger integer types. Of course you may possibly suffer a slight performance hit in these cases since the std::to_string call is unnecessary.

Note: I would have just added this in a comment to the original answer, but I don't have the rep to comment.

Oao answered 24/5, 2018 at 19:58 Comment(4)
in my testing std::to_string(i) does not print std::uint8_t integers as hex. I think this may have to have separate conditions for both uint8_t and int8_t types, since they need to be cast to larger integers.Scarlettscarp
@Scarlettscarp You are right. I don't know what I was doing at the time (months ago now) that made me thing to_string handled 8-bit ints. I even went back to the compiler version I think I was using back then, just to double check, but to_string didn't work as I said it did. So who knows? Anyway, thanks for catching this - I've edited answer to something that should work correctly.Oao
This will still work unexpectedly for char (which is distinct from both uint8_t and int8_t in most implementations (where they are respectively unsigned char and signed char)).Lilly
@ruslan Yes, also bool and wide char types all match std::is_integral and will not fail the assert. But since char is, per the std., a guaranteed unique type, as are the wide char types, you can handle all of them if desired (exceptions are un/signed char, which match the un/signed integral type of whatever width a byte is on the current machine - typically int8 - and so can't be filtered if you want to match ints of the same width as well). You can reject char, wide chars, bools by adding more terms to the the static_assert: ... && !std::is_same_v<char, T> && !std::is_same_v<bool, T> etc...Oao
J
3
int num = 30;
std::cout << std::hex << num << endl; // This should give you hexa- decimal of 30
Joettejoey answered 24/2, 2011 at 5:31 Comment(0)
H
2

My solution. Only integral types are allowed.

You can test/run on https://replit.com/@JomaCorpFX/ToHex

Update. You can set optional prefix 0x in second parameter.

definition.h

#include  <iomanip>
#include <sstream>

template <class T, class T2 = typename std::enable_if<std::is_integral<T>::value>::type>
static std::string ToHex(const T & data, bool addPrefix = true);



template<class T, class>
inline std::string ToHex(const T & data, bool addPrefix)
{
    std::stringstream sstream;
    sstream << std::hex;
    std::string ret;
    if (typeid(T) == typeid(char) || typeid(T) == typeid(unsigned char) || sizeof(T)==1)
    {
        sstream << static_cast<int>(data);
        ret = sstream.str();
        if (ret.length() > 2)
        {
            ret = ret.substr(ret.length() - 2, 2);
        }
    }
    else
    {
        sstream << data;
        ret = sstream.str();
    }
    return (addPrefix ? u8"0x" : u8"") + ret;
}

main.cpp

#include <iostream>
#include "definition.h"

int main()
{
    std::cout << ToHex<unsigned char>(254) << std::endl;
    std::cout << ToHex<char>(-2) << std::endl;
    std::cout << ToHex<int>(-2) << std::endl;
    std::cout << ToHex<long long>(-2) << std::endl;
    
    std::cout<< std::endl;
    std::cout << ToHex<unsigned char>(254, false) << std::endl;
    std::cout << ToHex<char>(-2, false) << std::endl;
    std::cout << ToHex<int>(-2, false) << std::endl;
    std::cout << ToHex<long long>(-2, false) << std::endl;
    return 0;
}

Results:

0xfe
0xfe
0xfffffffe
0xfffffffffffffffe

fe
fe
fffffffe
fffffffffffffffe

output

Holst answered 7/3, 2019 at 5:11 Comment(0)
P
2

_itoa_s

char buf[_MAX_U64TOSTR_BASE2_COUNT];
_itoa_s(10, buf, _countof(buf), 16);
printf("%s\n", buf);    // a

swprintf_s

uint8_t x = 10;
wchar_t buf[_MAX_ITOSTR_BASE16_COUNT];
swprintf_s(buf, L"%02X", x);
Picker answered 6/7, 2021 at 2:15 Comment(0)
M
2

ANOTHER SIMPLE APPROACH

#include<iostream> 
#include<iomanip> // for setbase(), works for base 8,10 and 16 only
using namespace std;


int main(){
  int x = (16*16+16+1)*15;
  string ans;


  stringstream ss;
  ss << setbase(16) << x << endl;


  ans = ss.str();


  cout << ans << endl;//prints fff
Mansized answered 1/6, 2022 at 8:7 Comment(1)
Too bad it doesn't support base 2... I have a case where that would be useful.Precambrian
N
1

For those of you who figured out that many/most of the ios::fmtflags don't work with std::stringstream yet like the template idea that Kornel posted way back when, the following works and is relatively clean:

#include <iomanip>
#include <sstream>


template< typename T >
std::string hexify(T i)
{
    std::stringbuf buf;
    std::ostream os(&buf);


    os << "0x" << std::setfill('0') << std::setw(sizeof(T) * 2)
       << std::hex << i;

    return buf.str().c_str();
}


int someNumber = 314159265;
std::string hexified = hexify< int >(someNumber);
Notum answered 3/11, 2014 at 22:31 Comment(1)
Shouldn't you just return buf.str() ?Ortego
O
1

Code for your reference:

#include <iomanip>
#include <sstream>
...
string intToHexString(int intValue) {

    string hexStr;

    /// integer value to hex-string
    std::stringstream sstream;
    sstream << "0x"
            << std::setfill ('0') << std::setw(2)
    << std::hex << (int)intValue;

    hexStr= sstream.str();
    sstream.clear();    //clears out the stream-string

    return hexStr;
}
Obola answered 18/2, 2016 at 20:22 Comment(4)
This doesn't really add to the existing answers, and it's pointless to explicitly clear the sstream (it will be destroyed when the function returns on the next line anyway). You could avoid the named hexStr entirely and just return sstream.str(); without clearing and get the same effect, reducing four lines of code to one.Domella
when purpose of the forum is to understand the things and usage. being verbose is far better to provide clear picture, than saving lines. question was not on optimized code, and answer tries to give a modular-method way of dealing with such conversions. @DomellaObola
What is the purpose of sstream.clear();? The sstream object is automatically destroyed at the end of the scope, so return sstream.str(); would do it.Acciaccatura
sstream.clear will just clear the content, before stream ends with scope end (to clear any fail and eof flags with clear). Indeed, when the scope dies, with the life-span of the stream-variable, and therefore sstream.str can be used to return by value. [Reference : cplusplus.com/reference/ios/ios/clear/]Obola
G
1

I do:

int hex = 10;      
std::string hexstring = stringFormat("%X", hex);  

Take a look at SO answer from iFreilicht and the required template header-file from here GIST!

Give answered 5/2, 2018 at 20:53 Comment(0)
C
1

I would like to add an answer to enjoy the beauty of C ++ language. Its adaptability to work at high and low levels. Happy programming.

public:template <class T,class U> U* Int2Hex(T lnumber, U* buffer)
{
    const char* ref = "0123456789ABCDEF";
    T hNibbles = (lnumber >> 4);

    unsigned char* b_lNibbles = (unsigned char*)&lnumber;
    unsigned char* b_hNibbles = (unsigned char*)&hNibbles;

    U* pointer = buffer + (sizeof(lnumber) << 1);

    *pointer = 0;
    do {
        *--pointer = ref[(*b_lNibbles++) & 0xF];
        *--pointer = ref[(*b_hNibbles++) & 0xF];
    } while (pointer > buffer);

    return buffer;
}

Examples:

char buffer[100] = { 0 };
Int2Hex(305419896ULL, buffer);//returns "0000000012345678"
Int2Hex(305419896UL, buffer);//returns "12345678"
Int2Hex((short)65533, buffer);//returns "FFFD"
Int2Hex((char)18, buffer);//returns "12"

wchar_t buffer[100] = { 0 };
Int2Hex(305419896ULL, buffer);//returns L"0000000012345678"
Int2Hex(305419896UL, buffer);//returns L"12345678"
Int2Hex((short)65533, buffer);//returns L"FFFD"
Int2Hex((char)18, buffer);//returns L"12"
Catacaustic answered 20/6, 2020 at 23:18 Comment(2)
This does use some techniques not seen in the other answers. Can you please edit the answer to include some explanation of how and why it works?Neoclassic
This would work on little endian CPUs but not on big endian ones.Setser
L
1

for fixed number of digits, for instance 2:

    static const char* digits = "0123456789ABCDEF";//dec 2 hex digits positional map
    char value_hex[3];//2 digits + terminator
    value_hex[0] = digits[(int_value >> 4) & 0x0F]; //move of 4 bit, that is an HEX digit, and take 4 lower. for higher digits use multiple of 4
    value_hex[1] = digits[int_value & 0x0F]; //no need to move the lower digit
    value_hex[2] = '\0'; //terminator

you can also write a for cycle variant to handle variable digits amount

benefits:

  • speed: it is a minimal bit operation, without external function calls
  • memory: it use local string, no allocation out of function stack frame, no free of memory needed. Anyway if needed you can use a field or a global to make the value_ex to persists out of the stack frame
Lapsus answered 14/9, 2020 at 10:28 Comment(0)
D
1

char_to_hex returns a string of two characters

const char HEX_MAP[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

char replace(unsigned char c)
{
    return HEX_MAP[c & 0x0f];
}

std::string char_to_hex(unsigned char c)
{
    std::string hex;

    // First four bytes
    char left = (c >> 4);
    // Second four bytes
    char right = (c & 0x0f);

    hex += replace(left);
    hex += replace(right);

    return hex;
} 
Dangle answered 27/7, 2021 at 14:50 Comment(0)
P
1

With the variable:

char selA[12];

then:

snprintf(selA, 12, "SELA;0x%X;", 85);

will result in selA containing the string SELA;0x55;

Note that the things surrounding the 55 are just particulars related to the serial protocol used in my application.

Pharmacognosy answered 3/6, 2022 at 21:27 Comment(2)
You should always use snprintf() to make 100% sure that you never overflow your buffer.Precambrian
Thank you. I've improved both the answer and my application!Pharmacognosy
C
0
#include <iostream> 
#include <sstream>  

int main()
{
unsigned int i = 4967295; // random number
std::string str1, str2;
unsigned int u1, u2;

std::stringstream ss;

Using void pointer:

// INT to HEX
ss << (void*)i;       // <- FULL hex address using void pointer
ss >> str1;          //     giving address value of one given in decimals.
ss.clear();         // <- Clear bits
// HEX to INT
ss << std::hex << str1;   // <- Capitals doesn't matter so no need to do extra here
ss >> u1;
ss.clear();

Adding 0x:

// INT to HEX with 0x
ss << "0x" << (void*)i;   // <- Same as above but adding 0x to beginning
ss >> str2;
ss.clear();
// HEX to INT with 0x
ss << std::hex << str2;   // <- 0x is also understood so need to do extra here
ss >> u2;
ss.clear();

Outputs:

std::cout << str1 << std::endl; // 004BCB7F
std::cout << u1 << std::endl;   // 4967295
std::cout << std::endl;
std::cout << str2 << std::endl; // 0x004BCB7F
std::cout << u2 << std::endl;   // 4967295


return 0;
}
Chough answered 27/7, 2020 at 21:48 Comment(0)
B
0

To convert an integer to a Hex string in C++ 17, you can use the following code using std::stringstream:

#include <iostream>
#include <sstream>

std::string to_hex_str(int hex_val)
{
    if (hex_val == 0) {
        return "0x0";
    }
    else if (hex_val < 0) {
        hex_val = -hex_val;
    }

    std::stringstream ss;
    ss << "0x" << std::hex << hex_val;
    return ss.str();
}

int main()
{
    int decimal_value = 512;
    std::cout << "Decimal value: " << decimal_value << ", Hexadecimal value: " << to_hex_str(decimal_value) << std::endl;

    return 0;
}

In this we also handle egde cases for negative values as well and i created method for this to access for ease of use.

Bib answered 25/9, 2021 at 17:34 Comment(0)
Q
0

This question is quite old but the answers given are to my opinion not the best. If you are using C++20 then you have the option to use std::format which is a very good solution. However if you are using C++11/14/17 or below you will not have this option.

Most other answers either use the std::stringstream or implement their own conversion modifying the underlying string buffer directly by themselves. The first option is rather heavy weight. The second option is inherently insecure and bug prone.

Since I had to implement an integer to hex string lately I chose to do a a true C++ safe implementation using function overloads and template partial specialization to let the compiler handle the type checks. The code uses sprintf (which one of its flavors is generally used by the standard library for std::to_string). And it relies on template partial specialization to correctly select the right sprintf format and leading 0 addition. It separately and correctly handles different pointer sizes and unsigned long sizes for different OSs and architectures. (4/4/4, 4/4/8, 4/8/8)

This answer targets C++11

H File:

#ifndef STRINGUTILS_H_
#define STRINGUTILS_H_

#include <string>

namespace string_utils
{
    /* ... Other string utils ... */
    
    std::string hex_string(unsigned char v);
    std::string hex_string(unsigned short v);
    std::string hex_string(unsigned int v);
    std::string hex_string(unsigned long v);
    std::string hex_string(unsigned long long v);
    std::string hex_string(std::ptrdiff_t v);

} // namespace string_utils

#endif

CPP File

#include "stringutils.h"

#include <cstdio>

namespace
{
    template <typename T, int Width> struct LModifier;

    template <> struct LModifier<unsigned char, sizeof(unsigned char)>
    {
        static constexpr char fmt[] = "%02hhX";
    };
    template <> struct LModifier<unsigned short, sizeof(unsigned short)>
    {
        static constexpr char fmt[] = "%04hX";
    };
    template <> struct LModifier<unsigned int, sizeof(unsigned int)>
    {
        static constexpr char fmt[] = "%08X";
    };
    template <> struct LModifier<unsigned long, 4>
    {
        static constexpr char fmt[] = "%08lX";
    };
    template <> struct LModifier<unsigned long, 8>
    {
        static constexpr char fmt[] = "%016lX";
    };
    template <> struct LModifier<unsigned long long, sizeof(unsigned long long)>
    {
        static constexpr char fmt[] = "%016llX";
    };
    template <> struct LModifier<std::ptrdiff_t, 4>
    {
        static constexpr char fmt[] = "%08tX";
    };
    template <> struct LModifier<std::ptrdiff_t, 8>
    {
        static constexpr char fmt[] = "%016tX";
    };

    constexpr char LModifier<unsigned char, sizeof(unsigned char)>::fmt[];
    constexpr char LModifier<unsigned short, sizeof(unsigned short)>::fmt[];
    constexpr char LModifier<unsigned int, sizeof(unsigned int)>::fmt[];
    constexpr char LModifier<unsigned long, sizeof(unsigned long)>::fmt[];
    constexpr char LModifier<unsigned long long, sizeof(unsigned long long)>::fmt[];
    constexpr char LModifier<std::ptrdiff_t, sizeof(std::ptrdiff_t)>::fmt[];

    template <typename T, std::size_t BUF_SIZE = sizeof(T) * 2U> std::string hex_string_(T v)
    {
        std::string ret(BUF_SIZE + 1, 0);
        std::sprintf((char *)ret.data(), LModifier<T, sizeof(T)>::fmt, v);
        return ret;
    }
} // anonymous namespace

std::string string_utils::hex_string(unsigned char v)
{
    return hex_string_(v);
}
std::string string_utils::hex_string(unsigned short v)
{
    return hex_string_(v);
}
std::string string_utils::hex_string(unsigned int v)
{
    return hex_string_(v);
}
std::string string_utils::hex_string(unsigned long v)
{
    return hex_string_(v);
}
std::string string_utils::hex_string(unsigned long long v)
{
    return hex_string_(v);
}
std::string string_utils::hex_string(std::ptrdiff_t v)
{
    return hex_string_(v);
}

Quay answered 3/3, 2022 at 9:37 Comment(0)
S
0

All the answers I read are pretty slow, except one of them, but that one only works for little endian CPUs. Here's a fast implementation that works on big and little endian CPUs.

std::string Hex64(uint64_t number)
{
    static const char* maps = "0123456789abcdef";

    // if you want more speed, pass a buffer as a function parameter and return an std::string_view (or nothing)
    char  buffer[17]; // = "0000000000000000"; // uncomment if leading 0s are desired
    char* c          = buffer + 16;
    do
    {
        *--c = maps[number & 15];
        number >>= 4;
    }
    while (number > 0);

    // this strips the leading 0s, if you want to keep them, then return std::string(buffer, 16); and uncomment the "000..." above
    return std::string(c, 16 - (c - buffer));
}

Compared to std::format and fmt::format("{:x}", value), I get between 2x (for values > (1ll << 60)) and 6x the speed (for smaller values).

Examples of input/output:

const std::vector<std::tuple<uint64_t, std::string>> vectors = {
    {18446744073709551615, "ffffffffffffffff"},
    { 4294967295u,         "ffffffff"},
    { 16777215,            "ffffff"},
    { 65535,               "ffff"},
    { 255,                 "ff"},
    { 16,                  "10"},
    { 15,                  "f"},
    { 0,                   "0"},
};
Setser answered 17/12, 2022 at 23:4 Comment(0)
J
-3

You can use this template function

template <class T>
std::string IntToHexForm(T a)
{
    std::string res;
    
    for (int i = 0; i < sizeof(T); i++)
    {
        int lowByte = a % 16;
        a /= 16;
        
        int highByte = a % 16;
        a /= 16;

        if (highByte > 9)
            res = std::string(1, 'a' + highByte - 10) + res;
        else
            res = std::to_string(highByte) + res;

        if (lowByte > 9)
            res = std::string(1, 'a' + lowByte - 10) + res;
        else
            res = std::to_string(lowByte) + res;
    }

    return res;
}

You can call it with different integers, for example:

  1. IntToHexForm(10) will return 00000010
  2. IntToHexForm((unsigned char)10) will return 10
Journalist answered 21/3, 2023 at 15:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.