The usual way to convert a string of digits like "0x3f947ae147ae147b"
into an actual integer is with one of the "strto" functions. Since you have 64 bits, and you're not interested in treating them as a signed integer (since you're about to, instead, try to treat them as a double
), the appropriate choice is strtoull
:
#include <stdlib.h>
char *str = "0x3f947ae147ae147b";
uint64_t x = strtoull(str, NULL, 16);
Now you have your integer, as you can verify by doing
printf("%llx\n", x);
But now the question is, how do you treat those bits as an IEEE-754 double
value, instead of an integer? There are at least three ways to do it, in increasing levels of portability.
(1) Use pointers. Take a pointer to your integer value x
, change it do a double
pointer, then indirect on it, forcing the compiler to (try to) treat the bits of x
as if they were a double
:
double *dp = (double *)&x;
double d = *dp;
printf("%f\n", d);
This was once a decent and simple way to do it, but it is no longer legal as it runs afoul of the "strict aliasing rule". It might work for you, or it might not. Theoretically this sort of technique can also run into issues with alignment. For these reasons, this technique is not recommended.
(2) Use a union:
union u { uint64_t x; double d; } un;
un.x = strtoull(str, NULL, 16);
printf("%f\n", un.d);
Opinions differ on whether this technique is 100% strictly legal. I believe it's fine in C, but it may not be in C++. I'm not aware of machines where it won't work.
(3) Use memcpy
:
#include <string.h>
uint64_t x = strtoull(str, NULL, 16);
double d;
memcpy(&d, &x, 8);
printf("%f\n", d);
This works by, literally, copying the individual bytes of the unsigned long int
value x
into the bytes of the double
variable d
. This is 100% portable (as long as x
and d
are the same size). I used to think it was wasteful, due to the extra function call, but these days it's a generally recommended technique, and I'm told that modern compilers are smart enough to recognize what you're trying to do, and emit perfectly efficient code (that is, just as efficient as techniques (1) or (2)).
Now, one other portability concern is that this all assumes that type double
on your machine is in fact implemented using the same IEEE-754 double-precision format as your incoming hex string representation. That's actually a very safe assumption these days, although it's not strictly guaranteed by the C standards. If you like to be particularly careful about type correctness, you might add the lines
#include <assert.h>
assert(sizeof(uint64_t) == sizeof(double));
and change the memcpy
call (if that's what you end up using) to
memcpy(&d, &x, sizeof(double));
(But note that these last few changes only guard against unexpected system-specific discrepancies in the size of type double
, not its representation.)
One further point. Note that one technique which will most definitely not work is the superficially obvious
d = (double)x;
That line would perform an actual conversion of the value 0x3f947ae147ae147b. It won't just reinterpret the bits. If you try it, you'll get an answer like 4581421828931458048.000000. Where did that come from? Well, 0x3f947ae147ae147b in decimal is 4581421828931458171, and the closest value that type double
can represent is 4581421828931458048. (Why can't type double
represent the integer 4581421828931458171 exactly? Because it's a 62-bit number, and type double
has at most 53 bits of precision.)