Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to cast a struct of 2 uint into a double

Tags:

c

I have a struct like below:

struct pts_t 
{
    uint32_t lsb;
    uint32_t msb;
};

I would like cast that into a double. Is it safe of directly write:

pts_t t; 

double timestamp = t;

and more complex, if the struct type is part of a C dll API, without having "packing" attribute (for all compiler) in that case I have to copy the pts_t*receive througth the API to pts_t instance i create to control the struct packing ?

void f(pts_t* t)
{ 
   pts_t myt; myt.lsb = t->lsb; myt.msb = t->msb;

   double timestamp = *(double*)(&myt.lsb);
}
like image 623
alexbuisson Avatar asked Aug 09 '13 07:08

alexbuisson


2 Answers

Even if you assume that your double are 64 bit wide, if you are looking for portable code you should be really careful with that: your structure and double might have different alignment constraints and your compiler may get confused because of aliasing rules. A way to avoid problems with that is to use a union

union {
 struct pts_t pts;
 double timestamp;
} x = { .pts = t };

and then use x.timestamp.

Also be careful that "composing" doubles like that might result in strange values such as infinities that you wouldn't encounter otherwise.

like image 186
Jens Gustedt Avatar answered Nov 03 '22 19:11

Jens Gustedt


The initial thought would be to write the following:

double timestamp = *( ( double * ) &( t.lsb ) );

To step through this (assuming you are in a 32-bit environment):

  1. You are getting the address of the identifier t.lsb because you need to find the memory address of the first byte in your structure. Note, you can alternatively do &t.
  2. You are then casting that memory address to be a pointer to a double (8 bytes).
  3. You are lastly dereferencing that pointer and storing all 8 bytes in the 8 byte block of memory that the identifier timestamp uses.

Remark:

  1. You will need to consider little/big endianness.
  2. You are assuming that both the structure and double are aligned properly (as mentioned below).
  3. This is not portable, you are assuming that a double is 8 bytes.

Now, the three points in the remark blurb are a lot to worry about. It becomes a big pain when porting this code accross multiple platforms. As mentioned below, using C unions is a much better and correct solution that is portable.

It would be written as follows with C unions:

double timestamp = ( union { double d; struct pts_t pts; } ) { t } .d;
like image 3
Jacob Pollack Avatar answered Nov 03 '22 19:11

Jacob Pollack