0

I was given a task at the university, there is a number and I need to display it in HEX as it is presented on the computer. I wrote a program for translating signed integers. And I also found a real number entry in HEX. But it is different from the usual.

For integers i use: printf("%#X", d);

For reals i use: printf("%#lX", r);

If i input 12, first prints: 0xC

If i input 12.0, second prints: 0x4028000000000000

Can you explain what the difference and how it's calculate?

Sam
  • 17
  • 1
  • 3
  • 1
    The `%x` format is for *integers*, not for floating point values. Mismatching format specifier and argument type leads to *undefined behavior*. – Some programmer dude May 09 '20 at 19:05
  • 1
    The difference is that `12` is an integer and `12.0` is a floating-point number whose binary representation follows [IEEE 754](https://en.m.wikipedia.org/wiki/IEEE_754). – mkrieger1 May 09 '20 at 19:05
  • “ 0x4028000000000000” is a hexadecimal representation of the bits that encode 12 in the IEEE-754 binary64 format. It was printed because, although you used `printf` incorrectly, your C implementation happened to access those bits and format them for display. This is **not** the way you should display the representation of floating-point numbers. – Eric Postpischil May 09 '20 at 19:24
  • 1
    With this following your previous question, it seems you are trying to print floating-point numbers in some hexadecimal format. But we do not know which. Saying it is “HEX” is not sufficiently informative. Numbers like 6+7/16+13/256 can be written in hexadecimal such as 6.7D, where the notation is the same as we use for decimal numbers except the base is 16 instead of ten. Or we can use IEEE-754 binary64 to encode the number in a special format of 64 bits, and then we can display those bits using hexadecimal. If you want help displaying the number, you must tell us which of these you want. – Eric Postpischil May 09 '20 at 19:32
  • To print in hexadecimal the IEEE-754 binary64 encoding of a `double` named `x`, given that your C implementation uses that format for `double`, include the headers ``, ``, ``, and `` and use `uint64_t u; memcpy(&u, &x, sizeof u); printf("%0x016" PRIx64 "\n", u);`. – Eric Postpischil May 10 '20 at 01:30
  • @sam To convert the encoding of any object to hex can be done with a modification of [To convert any variable/object to a string](https://stackoverflow.com/a/35367414/2410359) which employs binary. – chux - Reinstate Monica May 10 '20 at 03:11

3 Answers3

2

The X format specifier expects an int or unsigned int argument. With the l modifier it expects a long or unsigned long int argument. If you call it with anything else (such as a double) you get undefined behavior.

If you want to print a hex float (with uppercase letters), use %A format, which for 12.0 will print 0X1.8P+3 -- 1½×23

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
  • I got confused. Because in my internet research I found [sites](https://gregstoll.com/~gregstoll/floattohex/). It translates in the same way as this code. Your solution works, but in my task it is written to print 4 integers in HEX, which are a representation of this number. In my opinion, 0x1P + 0 is not quite what you need, or am I mistaken? – Sam May 09 '20 at 19:25
  • That site shows the conversion between bits and IEEE754 float representation, which may or may not be what your computer uses (most do these daya). – Chris Dodd May 09 '20 at 19:30
  • @sam Do you want to print the _value_ or the _encoding_ of the number? – chux - Reinstate Monica May 09 '20 at 19:30
  • @chux-ReinstateMonica **encoding** of the number – Sam May 09 '20 at 19:48
2

Printing double value r using format %#lX actually has undefined behavior.

You have been lucky to get the 64-bits that represent the value 12.0 as a double, unless r has type unsigned long and was initialized from the value 12.0 this way:

unsigned long r;
double d = 12.0;
memcpy(&r, &d, sizeof r);
printf("%#lX", r);

But type unsigned long does not have 64-bits on all platforms, indeed it does not on the 32-bit intel ABI. You should use the type uint64_t from <stdint.h> and the conversion format from <inttypes.h>:

#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <string.h>

int main() {
    int x = 12;
    printf("int: %#X  [", x);
    for (size_t i = 0; i < sizeof x; i++) {
        printf(" %02X", ((unsigned char *)&x)[i]);
    }
    printf(" ]\n");

    double d = 12.0;
    uint64_t r;
    memcpy(&r, &d, sizeof r);
    printf("double: %#"PRIX64" [", r);
    for (size_t i = 0; i < sizeof d; i++) {
        printf(" %02X", ((unsigned char *)&d)[i]);
    }
    printf(" ]\n");
    printf("sign bit: %d\n", (int)(r >> 63));
    printf("exponent: %d\n", (int)((r >> 52) & 2047));
    unsigned long long mantissa = r & ((1ULL << 52) - 1);
    printf("mantissa: %#llX, %.17f\n",
           mantissa, 1 + (double)mantissa / (1ULL << 52));
    return 0;
}

Output:

int: 0XC  [ 0C 00 00 00 ]
double: 0X4028000000000000 [ 00 00 00 00 00 00 28 40 ]
sign bit: 0
exponent: 1026
mantissa: 0X8000000000000, 1.50000000000000000

As explained in the article Double-precision floating-point format, this representation corresponds to a positive number with value 1.5*21026-1023, ie: 1.5*8 = 12.0.

chqrlie
  • 131,814
  • 10
  • 121
  • 189
0

To produce the encoding of a number in hex is a simple memory dump.

The process is not so different among types.


The below passes the address of the object and its size to form a string for printing.

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

//                                    .... compound literal ....................
#define VAR_TO_STR_HEX(x) obj_to_hex((char [(sizeof(x)*CHAR_BIT + 3)/4 + 1]){""}, &(x), sizeof (x))

char *obj_to_hex(char *dest, void *object, size_t osize) {
  const unsigned char *p = (const unsigned char *) object;
  p += osize;
  char *s = dest;
  while (osize-- > 0) {
    p--;
    unsigned i = (CHAR_BIT + 3)/4;
    while (i-- > 0) {
      unsigned digit = (*p >> (i*4)) & 15;
      *s++ = "0123456789ABCDEF"[digit];
    }
  }
  *s = '\0';
  return dest;
}

int main(void) {
  double d = 12.0;
  int i = 12;
  printf("double %s\tint %s\n", VAR_TO_STR_HEX(d), VAR_TO_STR_HEX(i) );
  d = -d;
  i = -i;
  printf("double %s\tint %s\n", VAR_TO_STR_HEX(d), VAR_TO_STR_HEX(i) );
  return 0;
}

Output

double 4028000000000000 int 0000000C
double C028000000000000 int FFFFFFF4

With more complex objects, the output may include padding bits/bytes and the output is sensitive to endian.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256