11

I have the following code:

short myShort = 23948;
byte myByte = (byte)myShort;

Now I wasn't expecting myByte to contain the value 23948. I would have guessed that it would contain 255 (I believe the largest value for a byte).

However, it contains 140, and it made me wonder why; what is actually going on behind the scenes?

Please note that I am not looking for someone to solve the problem that 23948 cannot fit into a byte, I am merely wondering about the underlying implementation

Fiona - myaccessible.website
  • 14,481
  • 16
  • 82
  • 117

10 Answers10

15

Short is a 2-byte type and a byte is, well, a single byte. When you cast from two bytes to one you're forcing the system to make things fit and one of the original bytes (the most significant) gets dropped and data is lost. What is left from the value of 23948 (binary: 0101 1101 1000 1100) is 140 which in binary translates to 1000 1100. So you are going from:

0101 1101 1000 1100 (2 byte decimal value 23948)

to:

          1000 1100 (1 byte decimal value 140)

You can only do this with an explicit cast. If you tried assigning a short to a byte without a cast the compiler would throw an error because of the potential for loss of data:

Cannot implicitly convert type 'short' to 'byte'. An explicit conversion exists (are you missing a cast?)

If you cast from a byte to a short on the other hand you could do it implicitly since no data would be getting lost.

using System;
public class MyClass
{
    public static void Main()
    {
        short myShort = 23948;
        byte myByte = (byte)myShort; // ok
        myByte = myShort; // error: 

        Console.WriteLine("Short: " + myShort);
        Console.WriteLine("Byte:  " + myByte);

        myShort = myByte; // ok

        Console.WriteLine("Short: " + myShort);
    }
}

With arithmetic overflow and unchecked context:

using System;
public class MyClass {
    public static void Main() {
        unchecked {
            short myShort = 23948;
            byte myByte = (byte)myShort; // ok
            myByte = myShort; // still an error
            int x = 2147483647 * 2; // ok since unchecked
        }   
    }
}
Paul Sasik
  • 79,492
  • 20
  • 149
  • 189
  • There's an important gap here... even with your complete-looking sample, it could behave differently. It depends on whether the context is `checked` or `unchecked`, which depends on the *code*, but also on a compiler setting (or the "Check for arithmetic overflow/underflow" checkbox in VS) – Marc Gravell Sep 27 '11 at 21:16
  • Marc: I tested with checked/unchecked and the behavior seems different for casting and arithmetic overflow. In an unchecked context overflows are allowed but casting still has to be explicit. See my added snippet. (I'm running Snippet Compiler with .NET 3.5) So... un/checked context seems to have no effect on casting... – Paul Sasik Sep 27 '11 at 21:40
  • `unchecked` is the default; now make it `checked` instead to see the difference – Marc Gravell Sep 27 '11 at 22:04
  • I've tried both. The result for the cast part of the code `myByte = myShort; // still an error` are the same regardless of whether `checked` or `unchedked` is specified. The compiler error is always the same: `Cannot implicitly convert type 'short' to 'byte'. An explicit conversion exists (are you missing a cast?)` – Paul Sasik Sep 27 '11 at 22:14
  • 1
    the only line I'm talking about is the one from the OP's post: `byte myByte = (byte)myShort;` – Marc Gravell Sep 27 '11 at 22:23
6

Basically it just takes the last 8 bits... but in general, when you find some behaviour which surprises you, the next step should be to consult the spec. From section 6.2.1, with the extra emphasis mine, for the situation which is relevant in this case.

For a conversion from an integral type to another integral type, the processing depends on the overflow checking context (§7.6.12) in which the conversion takes place:

  • In a checked context, the conversion succeeds if the value of the source operand is within the range of the destination type, but throws a System.OverflowException if the value of the source operand is outside the range of the destination type.
  • In an unchecked context, the conversion always succeeds, and proceeds as follows.
    • If the source type is larger than the destination type, then the source value is truncated by discarding its “extra” most significant bits. The result is then treated as a value of the destination type.
    • If the source type is smaller than the destination type, then the source value is either sign-extended or zero-extended so that it is the same size as the destination type. Sign-extension is used if the source type is signed; zero-extension is used if the source type is unsigned. The result is then treated as a value of the destination type.
    • If the source type is the same size as the destination type, then the source value is treated as a value of the destination type.
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
4

It depends; in a checked context, you'll get a big fat exception; in an unchecked context (the default) you get to keep the data from the last byte, the same as if you did:

byte b = (byte)(value & 255);
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
3

In your specific case, the behavior is pretty cut and dry when you look at the bits for the value:

short myShort = 0x5D8C; // 23948
byte myByte = (byte)myShort; // myShort & 0xFF

Console.WriteLine("0x{0:X}", myByte); // 0x8C or 140
user7116
  • 63,008
  • 17
  • 141
  • 172
1

Only the last 8 bits are kept. 23948 in binary is 101110110001100b. The last 8 bits of that is 10001100b, which equals 140.

Jeffrey Sax
  • 10,253
  • 3
  • 29
  • 40
1

When you cast an integer type to a "smaller" integer type, only the lesser weight bits are considered. Mathematically, it's as if you used the modulo operation. So you get the value 140 because 23948 modulo 256 is 140.

Casting a long to an int would use the same mechanism.

Falanwe
  • 4,636
  • 22
  • 37
  • This is not always true! Try this: short s = 35000; If you make modulo 32767 it evaluate to 2233 but instead the compiler assign s -30536 that is the value according to the specification and thus this is the correct value – xdevel2000 Nov 26 '15 at 16:33
  • That's because casting to short is not similar to performing a modulo 32768 (short.MaxValue + 1), but to a modulo 65536 (the number of different values for a short). -30536 is congruant to 35000 modulo 65536, and in the short value range. – Falanwe Nov 26 '15 at 16:41
  • No, I don't think this is correct. First, short is not ushort so maximum value is 32767; second, 35000 modulo 65535 (not 65536) is 35000. I do not know, maybe I misunderstood something? – xdevel2000 Nov 26 '15 at 16:49
  • `short` has 65536 possible values: 32767 positive, 32768 negatives, and 0. Hence, every unchecked calculation using a short is operated as if modulo 65536. When you perform a cast, the runtime only considers the lesser bits (as the accepted answer explains quite well). Mathematically, it's the same as finding the congruant value that is in range. In that case, -30536, because 35000 is just too big. (Note that -30536 + 65536 = 35000). – Falanwe Nov 26 '15 at 17:50
1

The result is the same when you do:

byte myByte = (byte)(myShort & 0xFF);

Everything above the eight bit is simply thrown away. The lower eight bits of 23948 (0x5D8C) is 140 (0x8C).

Guffa
  • 687,336
  • 108
  • 737
  • 1,005
1

Uhm...because when you cast short (2 bytes) to byte (1 byte) it gets only the first byte, and, the first byte of 23948 represents 140.

marcoaoteixeira
  • 505
  • 2
  • 14
1

23948 % 256 = 140, most significant bytes was lost after conversion, so the output is 140

Łukasz Wiatrak
  • 2,747
  • 3
  • 22
  • 38
1

It's like when you have a two digit number, "97", and convert it to a one digit number, you lose the 9 and only keep the "7"

xpda
  • 15,585
  • 8
  • 51
  • 82
MatteKarla
  • 2,707
  • 1
  • 20
  • 29