5

So I've always understood casting means taking the binary of something, and interpreting it as the casted entity (truncating if necessary). However I've noticed in C#, you can take a double, which is a 64bit representation that includes exponent as well as value. You can cast it to a long, which is a 64bit representation of a integer with no exponent. Therefore, it seems to me that if you take a random decimal value (say 10.43245) stored in a double, and cast that to a long, you would get some random number that happens to be whatever that binary turns out to be in a long. However that is not what happens, you simply get the truncated decimal and a value of 10.

Is my definition of casting wrong? Or does c# bend the rules a little bit here to give the programmer moreso what they probably expect from such a cast (I also assume java probably does the same, what about c++?)

John Saunders
  • 160,644
  • 26
  • 247
  • 397
Kevin DiTraglia
  • 25,746
  • 19
  • 92
  • 138
  • 4
    Yes, your definition of casting is wrong. In C++ 10.43245 would be cast to 10 as well. – druckermanly Jul 23 '14 at 02:47
  • Relevant: http://ericlippert.com/2009/03/03/representation-and-identity/ – Blorgbeard Jul 23 '14 at 02:49
  • C++ is illustrative in that you have various casts to cover different scenarios. `reinterpret_cast<>` means keeps the bits, but `static_cast` means apply appropriate conversion. For a proper answer, see: http://stackoverflow.com/questions/332030/when-should-static-cast-dynamic-cast-const-cast-and-reinterpret-cast-be-used – Keith Jul 23 '14 at 02:59

6 Answers6

2

In c# cast is an explicit conversion. Section 6 of C# specification 5.0 deals with conversions. In particular section 6.2.1 covers how numerical explicit conversions are defined in the language. As a rule they try to preserve the numerical value being converted as much as it's possible.

Andrew Savinykh
  • 25,351
  • 17
  • 103
  • 158
2

From MSDN:

When you convert from a double or float value to an integral type, the value is truncated. If the resulting integral value is outside the range of the destination value, the result depends on the overflow checking context. In a checked context, an OverflowException is thrown, while in an unchecked context, the result is an unspecified value of the destination type.

Therefore, as you observed, when you cast a double to long which is an integral type, you will get the truncated value.

shree.pat18
  • 21,449
  • 3
  • 43
  • 63
2

The idea of casting is to convert data to another type with little to no data loss via said conversion. To be all technical, we'll quote the Casting and Type Conversions page:

Implicit conversions: No special syntax is required because the conversion is type safe and no data will be lost. Examples include conversions from smaller to larger integral types, and conversions from derived classes to base classes.

We'll focus here on "no data will be lost."

I think the issue you're facing here is where that conversion is happening, and more importantly, where we want to avoid data loss.

Following your example, a double 10.43245 cast to a long, we face a case where no matter what, there's a type-implicit loss of data: 10.43245 is not 10, nor is it your random string of digits. double will innately store more digits post-decimal place (particularly since long stores a solid zero), so converting this number will, no matter what, cause a loss of data.

As an aside, it's kind of like converting spoken words into Morse Code. We do it, because it's useful. But we still lose some valuable qualities of speech in the process. It's a tradeoff, where we choose to favor the importance of data-transmittance over the importance of conveying speech in a natural and emotional way.

So after the fact is accepted that we're facing data loss, the choice has to be made of where makes most sense to lose that data. We can lose it in a human-readable form, by preserving the bit-order and all that, that's where you get your random string of digits, or we can lose it in its original, binary form and preserve "as much of the number" as we can, which is, in this case, the floor: ten.

In essence, it's just another simple tradeoff that was made when the first languages were being established, and nobody wants to change a good thing. It's way, way, way more useful to have (long)10.43245 resolve as 10.

It's also important to remember, as a couple other's have pointed out, that this is just the default behavior. Because this cast exists, there's no reason to assume that you just can't convert direct binary values from one type to the other. There are methods in many languages, like .NET's BitConverter.DoubleToInt64Bits, which will do this one-to-one bit conversion. It just doesn't make sense as a typical use case, and language design, particularly for high-level languages like we primarily deal with, is a matter of making the typical use case as efficient and effective as we can.

Matthew Haugen
  • 12,916
  • 5
  • 38
  • 54
2

Nothing magical about it, but it has to be doing more than just a straight convert of bits. For instance, look at how your would convert a float to a integer in assembly:

[Reference: http://www.codeproject.com/Articles/10679/Convert-Float-to-Int-using-Assembly-Language-Progr]

int ftoi(float flt)
{
    int i;
    _asm
    {
        mov  eax,flt; //loaded mem to acc
        rcl  eax,1;   //left shift acc to remove the sign
        mov  ebx,eax; //save the acc
        mov  edx,4278190080; //clear reg edx;
        and  eax,edx; //and acc to retrieve the exponent
        shr  eax,24;
        sub  eax,7fh; //subtract 7fh(127) to get the actual power 
        mov  edx,eax; //save acc val power
        mov  eax,ebx; //retrieve from ebx
        rcl  eax,8;     //trim the left 8 bits that contain the power
        mov  ebx,eax; //store
        mov  ecx, 1fh; //subtract 17 h
        sub  ecx,edx; 
        mov  edx,00000000h;
        cmp  ecx,0;
        je   loop2;
        shr  eax,1;
        or   eax,80000000h;        
loop1:    
        shr  eax,1; //shift (total bits - power bits);
        sub  ecx,1;
        add  edx,1;
        cmp  ecx,0;
        ja   loop1;
loop2:  
        mov  i, eax;        

//check sign +/-        
sign:
        mov  eax,flt;
        and  eax,80000000h;
        cmp  eax,80000000h;
        je     putsign;
    }

    return i;

putsign:
    return -i;
}

It's shifting over, then trimming, then subtracting, then shifting again.

Ben Humphrey
  • 588
  • 5
  • 17
1

Casting truncates the double. What you're looking for is BitConverter.DoubleToInt64Bits in C# or Double.doubleToLongBits in Java.

George
  • 377
  • 1
  • 3
1
long x = (long) doubleVal;

Is an example of an explicit conversion and will try to preserve the numerical value.

If you'd prefer to manipulate the bits of the double, you can either convert yourself using unsafe code:

long x;
unsafe
{
    x = *((long*) &val);
}

Or use the library function BitConverter.DoubleToInt64Bits

jaket
  • 9,140
  • 2
  • 25
  • 44