3

Somebody told me that type-casting C conversions does only change how the system interprets the information (for example, casting the char 'A' into int does return 65 when using cout to print it since in memory it stays as 01000001).

However, I noticed that, when casting floating point numbers into same width integers, the value is conserved and not changed, as it would be if only the interpretation was changed. For example, let X be a double precision floating point number:

double X = 3.14159;

As far as I now, when inspecting &X we will find (converted by decimal to binary converter):

01000000 00001001 00100001 11111001 11110000 00011011 10000110 01101110

But, as some of you would already know, when doing:

long long Y = (long long)X;

Y will be 3, the truncated version of X, instead of 4614256650576692846, the value it would get when looking at the binary values at &X if looking for a long long.

So, I think it is clear that they were wrong but, then, how does casting work in low level? Is there any detection of whether the value would be changed or not? How would you code it to get Y = 4614256650576692846 instead of Y = 3?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Néstor Llop
  • 85
  • 2
  • 9
  • 2
    All casts are different. *This* cast changes the bits. – user253751 Oct 28 '20 at 13:38
  • 1
    `memcpy(&Y, &X, sizeof(Y));` – MikeCAT Oct 28 '20 at 13:38
  • 1
    Whenever you feel the need to do a C-style type-cast in your C++ program, you should take that as a sign that you're doing something wrong. – Some programmer dude Oct 28 '20 at 13:40
  • `char` and `int` are (almost always) different sizes, so that's not the best example of the compiler just reinterpreting the bits. In the common case, the compiler must logically add three bytes worth of data to the `char` before it can be interpreted as `int`. And those bytes might even not all be 0 if the `char` is negative. It's far simpler to describe that conversion as creating an `int` with the same numeric value as the `char`. – chris Oct 28 '20 at 13:43
  • The cast is irrelevant. `long long Y = X;` would do exactly the same thing. – Pete Becker Oct 28 '20 at 15:19

3 Answers3

4

Casting will try to preserve the values as precise as possible. You can use memcpy() to copy bit patterns.

#include <iostream>
#include <cstring>

int main() {
    double X = 3.14159;
    long long Y;
    memcpy(&Y, &X, sizeof(Y));
    std::cout << Y << '\n';
    return 0;
}
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
MikeCAT
  • 73,922
  • 11
  • 45
  • 70
  • Just some nitpicking: There's no guarantee that `sizeof(long long) == sizeof(double)`. It just happens to be that now. Also, for dealing with bits, prefer to use `unsigned` integers. – Some programmer dude Oct 28 '20 at 13:43
2

Casting lets the compiler decide how to change the data in order for it to be as useful as possible yet respecting the requested datatype.

The int to char conversion just changes the interpretation from, let us say, 65 to 'A'.

However, when we have a value we may want to conserve, the compiler will use special instructions for its conversion.

For example, when casting from double to long long, the processor will use the CVTTSD2SI instruction, which loads and truncates a FP register's value into a general purpose one:

double a = 3.14159;
long long b = (long long)a;

will have a disassembly of (I got rid of the stack pointers for ease of understanding):

movsd   xmm0, QWORD PTR [a]
cvttsd2si       rax, xmm0
mov     QWORD PTR [b], rax

So, the ways to use the original value would be as mentioned in the selected answer: dereferencing the pointer to the double and place it into the long long variable or, as other stated, using memcpy().

Néstor Llop
  • 85
  • 2
  • 9
0

If you want to get Y = 4614256650576692846, you can use:

double X = 3.14159;
long long Y = *( (long long*)(&X) );

This will cast a double pointer to a long long pointer, and then the compiler thinks that (long long*)(&X) is somewhere a long long stores.

But I don't advise you to do so because the result is based on how double is stored on your machine, and the result is not guaranteed to be 4614256650576692846.

Jacder Zhang
  • 116
  • 5
  • 2
    Not just that, this cast is undefined behaviour. You're not allowed to alias a `double` with a `long long`. (For the most part, only `char`-based types are allowed to alias.) `bit_cast` would fix that while looking nicer. – chris Oct 28 '20 at 13:55
  • You need memcpy to safely and portably type-pun. Or C++20 [`std::bit_cast`](https://en.cppreference.com/w/cpp/numeric/bit_cast). The code in this answer is only safe with some implementations, like `g++ -fno-strict-aliasing`, or with MSVC always. But this question is tagged g++ and clang, so MSVC-specific type-punning is not a good answer, especially not without warning that it's UB in ISO C++, and a non-standard extension that isn't fully safe in most compilers. (It may happen to work in simple cases, but memcpy *always* works, and optimizes efficiently in modern compilers.) – Peter Cordes Oct 28 '20 at 19:11
  • 1
    **This is BAD code**. This answer violates strict aliasing. See [**What is the strict aliasing rule?**](https://stackoverflow.com/questions/98650/what-is-the-strict-aliasing-rule) It can also violate [**6.3.2.3 Pointers**, paragraph 7 of the C11 standard](https://port70.net/~nsz/c/c11/n1570.html#6.3.2.3p7) (other versions and C++ have similar restrictions): "A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned for the referenced type, the behavior is undefined." – Andrew Henle Dec 15 '20 at 13:30