Tyrael Archangel
Tyrael Archangel

Reputation: 487

Casting an int to a short gives unexpected results

I'm a little confused about loss information on numeric types in C#.

When i do this:

int x = 32780;
short y = (short)x;

I have the result: -32756 for y and not the expected 32767. Why? How this is calculated?

Range of short: -32768 to 32767 Range of int: -2,147,483,648 to 2,147,483,647

Upvotes: 19

Views: 2560

Answers (5)

Dan Puzey
Dan Puzey

Reputation: 34218

You seem to be expecting a “rounding down” effect rather than what is actually happening, which is a bitwise re-interpretation of the data.

In binary, x is equal to 00000000000000001000000000001100, which is a 32-bit number with only 16 significant bits. A short is a 16-bit signed integer which is represented using two’s complement notation.

When you convert, the last 16 bits of your x are being copied into y, giving 1000000000001100. Importantly here, the first digit is a 1. In two’s-complement notation, this is -32756. Your number hasn't been rounded—it’s been read as though it were 16-bit.

Upvotes: 53

Jeppe Stig Nielsen
Jeppe Stig Nielsen

Reputation: 62012

One could say that the runtime adds or subtracts 65536 as many times as is needed to get a number within the range of the short (System.Int16) type.

If you know a lot of pure math, instead of explaining it through the internal binary representation (which is also cool), you can understand it from modular arithmetic.

For this, consider the type short to be the integers modulo 65536, also written as ℤ/65536ℤ. Each member of ℤ/65536ℤ is a congruence class, i.e. a set of numbers all having the same remainder when divided by 65536. Now, each congruence class has an infinitude of different members ("representatives"). If you pick one, you get all the others by repeatingly adding or subtracting 65536.

With the short data type, we pick the unique representative in the interval -32768 through +32767. Then the ushort is the same thing, only do we pick the representative in 0 through 65535.

Now the cool thing about ℤ/65536ℤ is that it forms a ring in which we have addition, subtraction and multiplication (but not division). And actually, in unchecked context, with short x,y;, the C# operations

(short)(x + y)
(short)(x - y)
(short)(x * y)

correspond exactly to the arithmetic in ℤ/65536ℤ. (We have to cast back to short here because technically C# defines the operators only for int, uint, long, and ulong.)

In the same way, sbyte and byte can be thought of as the ring ℤ/256ℤ, int and uint as ℤ/4294967296ℤ, and long and ulong as ℤ/18446744073709551615ℤ.

Note, however, that because these moduli are not primes, division is not possible in the ring. For example, no int X satisfies

unchecked( 10 * X == 35 )   // integers Int32

and therefore it's not clear what 35/10 should be. On the other hand, two X satisfy

unchecked( 10 * X == 36 )   // integers Int32

but which of them should be 36/10?

However, exactly one int X makes

unchecked( 11 * X == 35 )   // integers Int32

true. We're having luck because 11 is relatively prime to 4294967296. The solution X is 1952257865 (check for yourself), so the quotient 35/11 is in a sense that number X.

Conclusion: The integer operations +, -, and * of C# can be interpreted as simply the ring operations in ℤ/nℤ. But the / operation is in no way related to the ring!

Upvotes: 6

eburgos
eburgos

Reputation: 2053

When you cast x to short you will get an overflow (i.e. the value being assigned more than it could handle) and your value would be represented as thought it would start over again starting from the min value. As you said, short's max value is 32767 so your new value would be: -32768 + (32780 - 32768) = -32756

Depending on the programming language (and perhaps your compiler), what you're trying to do could bring an exception or not. It seems in C# it doesn't.

Upvotes: 3

Dennis Traub
Dennis Traub

Reputation: 51694

There is an overflow. If you want the number to be capped to the maximum value, you can work around this by comparing the integer to short.MaxValue:

short y;
if (x > short.MaxValue) {
    y = short.MaxValue;
} else if (x < short.MinValue) {
    y = short.MinValue;
} else {
    y = (short)x;
}

Upvotes: 2

Phillip Schmidt
Phillip Schmidt

Reputation: 8818

Read Dan's answer for the "true" reason this happens, but it'll suffice to think about it like this: When a number overflows its maximum, it loops back around to the minimum. So, 32780 - 32767 = 13 and -32768 (which is one of the 13) + 12 (the other 12) = -32756

Upvotes: 14

Related Questions