Search code examples
c++doubleunionstype-punning

Is it safe to transport a double through an int in C++03?


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?


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.