75

Program in C#:

short a, b;
a = 10;
b = 10;
a = a + b; // Error : Cannot implicitly convert type 'int' to 'short'.

// we can also write this code by using Arithmetic Assignment Operator as given below

a += b; // But this is running successfully, why?

Console.Write(a);
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131

5 Answers5

79

There are two questions here. The first is "why is short plus short result in int?"

Well, suppose short plus short was short and see what happens:

short[] prices = { 10000, 15000, 11000 };
short average = (prices[0] + prices[1] + prices[2]) / 3;

And the average is, of course, -9845 if this calculation is done in shorts. The sum is larger than the largest possible short, so it wraps around to negative, and then you divide the negative number.

In a world where integer arithmetic wraps around it is much more sensible to do all the calculations in int, a type which is likely to have enough range for typical calculations to not overflow.

The second question is:

  • short plus short is int
  • assigning int to short is illegal
  • a +=b is the same as a = a + b
  • therefore short += short should be illegal
  • so why is this legal?

The question has an incorrect premise; the third line above is wrong. The C# specification states in section 7.17.2

Otherwise, if the selected operator is a predefined operator, if the return type of the selected operator is explicitly convertible to the type of x, and if y is implicitly convertible to the type of x or the operator is a shift operator, then the operation is evaluated as x = (T)(x op y), where T is the type of x, except that x is evaluated only once.

The compiler inserts the cast on your behalf. The correct reasoning is:

  • short plus short is int
  • assigning int to short is illegal
  • s1 += s2 is the same as s1 = (short)(s1 + s2)
  • therefore this should be legal

If it did not insert the cast for you then it would be impossible to use compound assignment on many types.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 13
    your first sample is not good. If you replace `short` with `int` everything will work fine, but if we put larger numbers (to make sum be greater than 2^31) it will cause int overflow. my question is why `short + short = int` (and for `byte`) and `int + int = int` (not long for example). – Andrey Dec 03 '10 at 17:17
  • **Why was it decided that int's shouldn't widen to longs?** I can understand the reasoning for widening the computation on bytes and shorts, but it seems just as likely that arithmetic on int could overflow as well. Is this just a concession to the fact that on 32bit architectures there are convenient instructions that maintain results in 32-bit registers? Or is there something deeper at play here? – LBushkin Dec 03 '10 at 17:21
  • 8
    @LBushkin: Is it really "just as likely"? I write compilers for a living and I deal every day with compiling programs that have more than, say, 2^15 types. If I am adding together the number of types in a bunch of different assemblies I could easily overflow a short. I've never had a problem in the compiler space where I could reasonably overflow an int. The compiler would run out of virtual memory long before it managed to do arithmetic involving a program that has two billion types in it. It simply is not likely in most scenarios to have 32 bit overflow. – Eric Lippert Dec 03 '10 at 17:31
  • 2
    @Andrey: Yes, integer arithmetic can overflow. If you want to avoid that then you can use the checked operator to ensure that integer overflows result in exceptions. If you feel that it is likely that you'll be working with integers large enough that common math operators on them can overflow then you should be doing all your arithmetic in longs in the first place. – Eric Lippert Dec 03 '10 at 17:33
  • 1
    @Eric Lippert i was just wondering why the behaviour is different for `short` and `int`. – Andrey Dec 03 '10 at 17:42
  • 7
    @Eric: int is used in many computational contexts, some of which have no relation to memory/process space. As computing hardware increases in power & capacity, such boundaries are going to be surpassed fairly soon on a regular basis. But perhaps a better way of expressing my point is that if the compiler decides to widen smaller types to wider types when performing arithmetic to avoid unanticipated overflow, it should do so with all types that have an available wider type that preserves range and representational accuracy. The choice to stop at int (and not widen to long) is a bit unexpected. – LBushkin Dec 03 '10 at 17:49
  • 1
    @Andrey: Because shorts and ints are *very different*. Most realistic calculations done in integers use only a tiny fraction of the range of an int; those same calculations use *all or more than all* of the range of a short. Most modern processors do all their integer math in 32 bits natively; you have to go way back in time to find processors that do work in 16 bits natively. I last worked on compilers for 16 bit Windows in 1997. – Eric Lippert Dec 03 '10 at 17:50
  • 5
    @LBushkin: OK, so would you have us (1) say that int + int is long? Doing so is tantamount to saying that *all calculations are always done in longs all the time*. You don't want to make people insert casts in every integer calculation. (2) have C# have different behaviour depending on the architecture, as C does? (3) automatically widen to a larger type, as VBScript does; abandon static typing in C#. (4) something else? Each of these approaches has serious performance or portability costs. Are the costs worth it? – Eric Lippert Dec 03 '10 at 17:53
  • 1
    @Eric: All of those are good points. To #1, C# is a language where all arithmetic is performed in ints all the time. Aside from the performance implications, I don't see a reason to prefer a narrower type to a wider one. Performance could very well be a deciding factor, however. To #2, I agree that different behavior on different architectures is undesirable and should be avoided. I don't think that we want to abandon static typing either. BTW, don't get me wrong; I'm just trying to understand the thinking behind the design decisions that go into creating a language like C#. – LBushkin Dec 03 '10 at 18:01
  • 1
    @Eric: I guess I would argue that at times it would be nice to have finer grained control of when type widening takes place. I've often wanted to perform calculation on bytes or shorts that I did not want to have occur via promotion to int (since I was essentially performing bitwise computations). A syntax (perhaps similar to the checked/unchecked blocks) to allow that would be nice. – LBushkin Dec 03 '10 at 18:03
  • 3
    @LBushkin: Why do you want to avoid such promotions? I assure you that both *the CLR runtime layer* and, ultimately, *the CPU* will widen those to int. Suppose for the sake of argument that we *wanted* to have short + short stay in shorts. The IL layer has no opcodes for that operation. It only has opcodes for integer addition. The CLR is going to convert those shorts to ints regardless. Suppose we added short addition operators to IL; now what is the jitter going to do? The chip is going to widen the operands to 32 bits when it enregisters them. – Eric Lippert Dec 03 '10 at 18:08
  • 1
    @Eric: Typically, bytes and shorts are used to represent numbers - and so you want them to behave with the semantics of numbers. But sometimes you just need a ordered bag of bits - and you want to use arithmetic operations for their side-effects. The ^, |, &, <<, and >> bitwise operators retain the type of the expression they are applied to - but sometimes it can be useful to if +, -, *, and / could do so as well. Arithmetic with wraparound is occasionally useful. However, I understand your point about whether it's sufficiently valuable for enough people to design, develop, and test for C#. – LBushkin Dec 03 '10 at 19:34
  • 4
    @Eric: Also, I was under the impression that at least on the x86 architecture, it's possible to perform move, load, and arithmetic on just the 16bit bit non-extended registers. And you could also access the low and high 8-bit subregisteres (eg. AL and AH within AX). Obviously, the behavior on non-x86 architectures could be substantively different. – LBushkin Dec 03 '10 at 19:42
  • @LBushkin Re: "Why was it decided that int's shouldn't widen to longs?" The CLI was designed to run only on 32-bit and 64-bit architectures, not narrower architectures. That's why widening to 32 bits (like C/C++) but also making `int` to be always 32 bits in size (unlike C/C++) is a good design choice. So it's guaranteed that 32-bit arithmetic can be done efficiently on any target architecture. This is not true for 64-bit integer arithmetic on 32-bit architectures (`long` has a fixed 64-bit size in C#). In addition, on x86, instructions with 32-bit operands generally... – Hadi Brais Jan 07 '22 at 05:45
  • ...have shorter lengths than with operands of other sizes. Promoting to `int`/`uint` strikes the best trade-off between range and portability of efficient arithmetic. Note that there is one case where 32-bit or narrower operands are promoted to `long`: if one operand is of type `uint` and the other is of type `sbyte`, `short`, `char`, or `int`, both are converted to `long`. Now if you do really want a narrow result (e.g. short+short=short), the impact of an explicit cast on the result of the widened operation on performance depends on whether the operation is in a checked or unchecked context. – Hadi Brais Jan 07 '22 at 05:46
  • On x86, using .NET 6.0.1, the JIT compiler emits one unnecessary instruction for the expression `unchecked((short)(a + b))` and four unnecessary instructions for the expression `checked((short)(a + b))` compared to doing directly 16-bit addition. This can be significant in a loop. This is a missed optimization in the JIT because for these simple expressions, it's relatively easy to determine that the widened result is immediately truncated, so it can just emit 16-bit addition directly. This is what would happen if you compile an expression that is equiv to `uncheck...` in C++ using MSVC 19.30. – Hadi Brais Jan 07 '22 at 05:47
14

Well, the += operator says you'll be increasing the value of a with a short, while = says you'll overwrite the value, with the result of an operation. The operation a + b yields an int, not knowing that it can do otherwise, and you're trying to assign that int to a short.

David Hedlund
  • 128,221
  • 31
  • 203
  • 222
8

You have to use:

a = (short)(a + b);

As to the difference between the behaviours of assignment and addition assignment, I imagine it has something to do with this (from msdn)

x+=y
is equivalent to
x = x + y
except that x is only evaluated once. The meaning of the + operator is
dependent on the types of x and y (addition for numeric operands, 
concatenation for string operands, and so forth).

However, it's a bit vague, so mabye someone with a deeper understanding can comment.

UpTheCreek
  • 31,444
  • 34
  • 152
  • 221
  • 1
    @jak: did you try it with both sets of parentheses? the code in your comment is different. – UpTheCreek Dec 03 '10 at 09:48
  • 2
    Where does it say that on MSDN? That is wrong **if the + is a predefined operator**, which in this case, clearly it is. In that case it is equivalent to x = (T)(x + y), as specified. If you can send me the link to the page, I'll bring it to the attention of the documentation managers. – Eric Lippert Dec 03 '10 at 17:06
  • 2
    @Eric: Sure, it's on this page: http://msdn.microsoft.com/en-us/library/sa7629ew.aspx – UpTheCreek Dec 04 '10 at 08:55
6

This happens because int is the smallest signed type for which + is defined. Anything smaller is first promoted to int. The += operator is defined with respect to +, but with a special-case rule for handling results that don't fit the target.

Marcelo Cantos
  • 181,030
  • 38
  • 327
  • 365
0

This is because += is implemented as an overloaded function (one of which is a short, and the compiler chooses the most specific overload). For the expression (a + b), the compiler widens the result to an int by default before assigning.

Stefan Mai
  • 23,367
  • 6
  • 55
  • 61
  • 1
    overloaded operator can only implemented with user defined operator like class , structure. but it is + an arithmetic operator, which is inbuilt.The += operator cannot be overloaded directly, but user-defined types can overload the + operator, for details visit link,,http://msdn.microsoft.com/en-us/library/sa7629ew(v=VS.71).aspx – Mohammad Jahangeer Ansari Dec 03 '10 at 08:39
  • 1
    jak is correct. The += operator is not an overloaded function in C#. – Eric Lippert Dec 03 '10 at 17:07