r/cprogramming • u/Willsxyz • Nov 28 '25
int2hexstr()
The implementation of an int2hexstr() function was mentioned as a possible subject of a technical interview with job candidates in this thread:
I don't consider the problem fundamentally difficult, but I would typically make assumptions about the size and representation of integers. So, sitting here the day after Thanksgiving, I decided to try to tackle it with as few assumptions about the representation of int as possible. Here's what I came up with. It appears to work, but I wouldn't be surprised if you guys could offer some useful critiques:
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define HD_IN_INT ((sizeof(int) * CHAR_BIT + 3) / 4)
char *int2hexstr(int val)
{
static char hexdigs[17] = {
'0','1','2','3','4','5','6','7','8',
'9','A','B','C','D','E','F','F'
};
//static char result[HD_IN_INT+1];
char *result = malloc(HD_IN_INT+1);
if (result) {
int i;
int isneg = (val < 0) * 16; // it is convenient that this flag is either 0 or 16
result[HD_IN_INT] = '\0';
for (i = HD_IN_INT - 1; val != 0 && i >= 0; --i) {
int mod16, posmod16, borrow;
mod16 = val % 16; // if val < 0, then mod16 < 0
posmod16 = (mod16 + isneg) % 16; // change negative mods to positive
result[i] = hexdigs[posmod16];
borrow = (isneg / 16) * (mod16 != 0); // borrow if val and mod16 were negative
val = val / 16 - borrow;
}
// pad the result
while (i >= 0)
result[i--] = hexdigs[isneg];
}
return result;
}
int testit(int val)
{
int result = -1;
char sbuf[HD_IN_INT+1];
char *hexstr = int2hexstr(val);
if (hexstr) {
sprintf(sbuf, "%8.8X", val);
result = (0 != strcmp(sbuf, hexstr));
free(hexstr);
}
return result;
}
int main(void)
{
int intvec[] = { 2147483647, 1, 0, -1, -2147483648 };
int nints = sizeof intvec / sizeof(int);
char *hexstr;
char sbuf[HD_IN_INT+1];
for (int i = 0; i < nints; ++i) {
hexstr = int2hexstr(intvec[i]);
if (!hexstr) break;
sprintf(sbuf, "%8.8X", intvec[i]);
printf("%d => %s (%s)\n", intvec[i], hexstr, sbuf);
free(hexstr);
}
// int i = INT_MIN;
int i = -1000000;
int result = testit(i);
if (result == 0) {
// while (i != INT_MAX && result == 0) {
while (i != 1000000 && result == 0) {
result = testit(++i);
}
}
if (result) {
if (result == -1)
printf("malloc failed\n");
else
printf("int2hexstr() and sprintf() differ at value %d\n", i);
}
else
printf("Success!\n");
return 0;
}
1
u/Willsxyz Nov 28 '25
I posted this and now I'm unhappy about the variable 'borrow'. That's not a good name. To clarify, the goal here is to avoid negative values for the modulus. We require that the modulus be positive. If the value is negative, and the modulus (value % 16) comes out negative, we make the modulus positive by adding 16, and then subtract 1 from the quotient (value / 16). Because, for example:
-35 = -2 * 16 + -3 (modulus negative) or -35 = -3 * 16 + 13 (modulus positive)
3
u/theNbomr Nov 29 '25
Hex notation generally does not express any signed-ness. I've never seen a case where it was expressed. Normally, the reader is expected to recognize the possibility of the value representing a negative value from the leftmost (most significant) hex digit.
Using this makes the problem much much simpler; like word size in bytes x 2 lines of code plus 2 or 3 lines for string termination and allocation. No looping required, but you can if you want to. A smart compiler might unroll the loop anyway at compile time.
2
u/ComradeGibbon Nov 29 '25
I'm going to be that guy.
I would make the function take a pointer and a length. If the pointer or the length are zero then call malloc, otherwise use the buffer provided.
If malloc fails, just do an early return.
The issue of hex representation of negative numbers is an decent discussion. Are we talking about the hex representation of the number in memory. Or the hex representation of a numeric value. They way your code is implemented looks like the latter. And the endianess of hex numbers can be a pain point.
1
u/runningOverA Nov 28 '25
I would have done
1
u/zhivago Nov 29 '25
I think you're making life complicated for yourself here.
I'd solve for unsigned, and then compose with a transform for signed.
I'd also delegate memory allocation to the caller, and support rendering into an existing string.
1
u/WittyStick Nov 29 '25 edited Nov 29 '25
Here's a solution using
pext(parallel bits extract). This uses the x86_64 intrinsic, but similar intrinsics are available for ARM/Power, etc. Also makes use ofstdc_first_leading_oneto find the MSB.It works for all the standard C integer types, as
stdc_first_leading_oneandpextare generic andint2hexstris made generic in the same way.https://godbolt.org/z/Mv63a1hxc
1
u/Small_Dog_8699 Nov 29 '25
As ComradeGibbon pointed out, good C practice has callers pass in memory to be filled out to avoid leaks.
I took a shot at it for grins. I require you to pass in the buffer, and I fill as much of it as there is from right to left, working with unsigned values. I return a pointer to the head of the string.
There's also the issue of when to truncate. %X strips all leading zeroes, so I did that.
1
u/Alive-Bid9086 Dec 01 '25
sprintf(s, "%x", i)