2

I can specify the number of digits like printf("%08x", ...). But I'd like to automatically adjust the number of digits according to the data type, (e.g, long (64 bits) should automatically have 64/4=16 digits). Is there a way to do so? Thanks.

user1424739
  • 11,937
  • 17
  • 63
  • 152
  • 1
    Take a closer look at e.g. [this `printf` (and family) reference](https://en.cppreference.com/w/c/io/fprintf). Pay close attention to what `*` means in a format specifier. Also don't forget what `sizeof` returns. – Some programmer dude Feb 22 '19 at 16:24
  • 2
    Even though there is a way: `printf("%0*x", sizeof(n)*2, n);` - it is not clear to me how you can benefit from it. Looks like usable in macros only. – Eugene Sh. Feb 22 '19 at 16:27
  • @EugeneSh. The `*` length specifier is certainly usable outside of macros; see the answers on https://stackoverflow.com/questions/54577653/is-there-a-branching-string-format-descriptor – Govind Parmar Feb 22 '19 at 16:39
  • 1
    @GovindParmar It is, no argument here. The requested usage is questionable. – Eugene Sh. Feb 22 '19 at 16:40
  • You'll need a length modifier as well as controlling the number of digits. – AProgrammer Feb 22 '19 at 16:41

2 Answers2

4

How to printf(“%0x”) with the number of digits determined from the type?

1) Find the bit width

Use sizeof, CHAR_BIT to find the bit width sizeof(x)*CHAR_BIT1 or see below alternative.

2) Find the nibble width

(bit_width + 3)/4 for the nibble width - number of hexadecimal characters.

3) Pass the width

Use "*" to pass in the width and print using the widest type, uintmax_t.

#include <float.h>
#include <limits.h>
#include <stdio.h>

#define PRINTF_XW(x) printf("%0*jx\n", (int)(sizeof(x)*CHAR_BIT+3)/4, (uintmax_t) (x))

int main(void) {
  unsigned char uc = 1;
  unsigned short us = 2;
  unsigned u = 3;
  unsigned long ul = 4;
  unsigned long long ull = 5;
  uintmax_t uj = 6;

  PRINTF_XW(uc);
  PRINTF_XW(us);
  PRINTF_XW(u);
  PRINTF_XW(ul);
  PRINTF_XW(ull);
  PRINTF_XW(uj);
  return 0;
}

Sample output, may differ amongst platforms.

01
0002
00000003
0000000000000004
0000000000000005
0000000000000006

Alternative

Another approach would use _Generic - tis a bit more work - and more rewards.
See unsigned long long uf:42; example below.

#include <float.h>
#include <limits.h>
#include <stdint.h>

int hexwidth(uintmax_t u) {
  int count = 3;
  while (u) {
    u >>= 1;
    count++;
  }
  return count/4;
}

// Default case useful for other unsigned types wider than `unsigned`
#define HEXWIDTH(s) _Generic((s), \
    unsigned char: hexwidth(UCHAR_MAX), \
    unsigned short: hexwidth(USHRT_MAX), \
    unsigned : hexwidth(UINT_MAX), \
    unsigned long: hexwidth(ULONG_MAX), \
    unsigned long long: hexwidth(ULLONG_MAX), \
    default: hexwidth((s)*0u - 1u) \
    )

#define PRINTF_XW(x) printf("%0*jx\n", HEXWIDTH(x), (uintmax_t) (x))

With language extensions that allow wider than unsigned fields - works nicely too.

int main(void) {
  struct {
      unsigned long long uf:42;
  } s1 = {7};
  struct {
      unsigned long long uf:51;
  } s2 = {8};

  PRINTF_XW(s1.uf);
  PRINTF_XW(s2.uf);
}

Output

00000000007   <-- 11 digits
0000000000008 <-- 13 digits

Macro-magic could replace hexwidth(uintmax_t u) as follows with a compile time calculation:

#define NW3(x) (((x) > 0xFu) ? 2 : 1)
#define NW4(x) (((x) > 0xFFu) ? NW3((x)>>8)+2 : NW3(x))
#define NW5(x) (((x) > 0xFFFFu) ? NW4((x)>>16)+4 : NW4(x))
#define NW6(x) (((x) > 0xFFFFFFFFu) ? NW5((x)>>32)+8 : NW5(x))
#define NW(x) (((x) > 0xFFFFFFFFFFFFFFFFu) ? NW6((((x)+0llu)>>32)>>32)+16 : NW6((x)+0llu))

// Default case useful for all unsigned types as wide/wider than `unsigned`
#define HEXWIDTH(s) _Generic((s), \
    unsigned char: NW(UCHAR_MAX), \
    unsigned short: NW(USHRT_MAX), \
    default: NW((s)*0u - 1u) \
    )

1 This works well when there is no padding in the type - common amongst standard unsigned integer types.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • At least for unsigned types, you can use the ceil of the base-16 log of `(1?-1:expr)` (the max value representable in the unsigned type of `expr`) rather than `sizeof` hacks that assume no padding. For signed types this is harder. – R.. GitHub STOP HELPING ICE Feb 22 '19 at 18:57
  • @R.. The `(1?-1:expr)` has a short-coming: promotes sub-`unsigned` types to `int` returning -1. Yet is another tool in the arsenal. – chux - Reinstate Monica Feb 22 '19 at 19:03
  • Indeed. If you don't need to write it in a macro, just write `(T)-1` or `T_MAX` directly, where `T` is the type, known statically. It seems you know this anyway if you have a format string to use with it. – R.. GitHub STOP HELPING ICE Feb 22 '19 at 19:14
0

You should probably look into C's <inttypes.h>

#include <inttypes.h>

unit8_t value = 0x34;
printf("The value formatted for 8 bits is %" PRIx8 ".", value);

Note the PRIx8 is a string, which correctly will format for "printf" "hexadecimal" "eight bits".

Note that the lack of commas between the strings are not a mistake. This is a formatting technique that uses a feature in C called "automatic string concatenation", such that the three strings will get slapped together. The PRIx8 is just a convenience which means the correct formatting for an 8 bit hexadecimal value.

To get the similar output formatted for 32 bits, you would use PRIx32.

For a brief overview of the inttypes.h output formats, you can look at Good introduction to <inttypes.h>

Edwin Buck
  • 69,361
  • 7
  • 100
  • 138
  • This is not an answer to OP's question. It's about matching the type, not the style for the max number of digits that might be needed. – R.. GitHub STOP HELPING ICE Feb 22 '19 at 18:58
  • @R.. I think you're focusing on the formatting, but since there is one formatting for every data type in `inttypes.h` there's a formatting to match every type too, as well as a more portable way of representing every type they are likely to use too. – Edwin Buck Feb 22 '19 at 20:09
  • OP is asking how to automatically select the right number of digits to print. – R.. GitHub STOP HELPING ICE Feb 22 '19 at 20:23
  • I agree with @R.. This does not answer the original question. – user1424739 Feb 22 '19 at 20:41
  • "there's a formatting to match every type too" --> `inttypes.h` has many, yet there are some troublesome holes in C and `printf()`: `int` _bit-fields_ have implementation sign-ness, so `"%d"` could be technical UB. Printing `char` numeric value likewise has a similar trouble when `CHAR_MAX > INT_MAX`. `clock_t, time_t` are most problematic as either might not even be an integer. – chux - Reinstate Monica Feb 22 '19 at 22:59