Question

EDIT: Skip down below the horizontal rule for my newest version of the question.

Assuming only that sizeof(double) * CHAR_BITS <= 64, is the assert in following program guaranteed by C++03 to always be satisfied?

union u {
  std::int64_t m_int;
  double       m_double;
};

int main()
{
  double d = get_a_random_double();

  u u1;
  u1.m_double = d;

  u u2;
  u2.m_int = u1.m_int;

  assert(u2.m_double == d);
}

I am worried that it might not be guaranteed by the standard that a copy of an int64_t value preserves all of the bits that we use to hold the double.

On the other hand, I am pretty sure that the following alternative, that uses chars instead of unt64_t, is guaranteed by the standard to always satisfy the assert:

struct chars {
  char m_x[sizeof(double)];
};

union u {
  chars  m_chars;
  double m_double;
};

int main()
{
  double d = get_a_random_double();

  u u1;
  u1.m_double = d;

  u u2;
  u2.m_chars = u1.m_chars;

  assert(u2.m_double == d);
}

Do you agree?


EDIT:

Ok, I must concede that conversions by means of a union are not supported by the standard.

So what about this one where I try to transport a double through a sequence of chars without using a union:

int main()
{
  double d, e;
  char c[sizeof(double)];

  char* p_d = static_cast<char*>(static_cast<void*>(&d));
  char* p_e = static_cast<char*>(static_cast<void*>(&e));

  d = get_a_random_double();

  // double -> chars
  std::copy(p_d, p_d+sizeof(double), c);

  // chars -> double
  std::copy(c, c+sizeof(double), p_e);

  assert(e == d);
}

Is this one guaranteed by the standard to always satisfy the assert?

EDIT: Yes! See C++11 3.9/2 "Types"

What about this next one where I try to transport the double trough an int64_t without the use of unions.

I know that int64_t is not a part of c++03, so lets talk c++11 instead.

Note again that I am assuming that sizeof(double) * CHAR_BITS <= 64.

int main()
{
  double d, e;
  std::int64_t i, j;
  char c[sizeof(std::int64_t)];

  char* p_d = static_cast<char*>(static_cast<void*>(&d));
  char* p_e = static_cast<char*>(static_cast<void*>(&e));
  char* p_i = static_cast<char*>(static_cast<void*>(&i));
  char* p_j = static_cast<char*>(static_cast<void*>(&j));

  d = get_a_random_double();

  // double -> chars -> std::int64_t
  std::copy(p_d, p_d+sizeof(double), c);
  std::copy(c, c+sizeof(std::int64_t), p_i);

  // std::int64_t -> std::int64_t
  j = i; // <------------ Are all bits preserved here?

  // std::int64_t -> chars -> double
  std::copy(p_j, p_j+sizeof(std::int64_t), c);
  std::copy(c, c+sizeof(double), p_e);

  assert(e == d);
}

As before, one of my concerns is whether copying one of the standard integers types (std::int64_t) could "corrupt" some bits ( for example, some bits that are not participating in the integer value representation). Or, are integer assignments guaranteed to always faithfully copy all bits of the bytes that the integer occupies?

Was it helpful?

Solution

No, in fact it's undefined behavior:

u u1;
u1.m_double = d;

u u2;
u2.m_int = u1.m_int;

You can't set u1.m_double and read u1.m_int.

Only one active union member is allowed at a time.

OTHER TIPS

The language itself doesn't guarantee that you can use any other type than char or unsigned char pointers to access other data types - and even that is fairly restricted.

It all depends on how portable you want your code to be. Some machines have bizarre ways to treat floating point numbers, and casting them around will do bad things.

In practice, MOST processors will be perfectly fine with what you've just done, and most compilers are perfectly happy to produce the "correct" code to overlay a 64-bit integer with a 64-bit floating point number, and transport it that way.

The trouble comes when you try to run this on a DSP, old IBM hardware, Cray YMP's, or something else that is a little unusual. Or if you start using very untested compilers.

So to some degree, it comes down to "how portable do you need it to be", and how well is your software tested for this to not creep through as a strange error that only occurs on Tuesdays in months with R in the name, and only on days that are evenly divisible with 7 and 3, with a full moon? [And inevitably, only by your most important customer that is in a country about 12 hours off your timezone].

All types with fixed sizes are c++11. Before that the standard only defined a minimum number of bits that the variable has to hold (most of them only guarantee to be at least as large as the previous one actually ...). so std::int64_t will be undefined in c++03 and you cannot rely on any sizes of other ints (including char!).


Also, as Luchian Grigore points out: writing to one union member and reading from another one is not defined by the (any) standard. Nevertheless while you are not guaranteed that the code you wrote will work, most compilers will interpret it in the way you intended.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top