8

This question stems from a bug where I iterated over a collection of Int64 and accidentally did foreach (int i in myCollection). I was trying to debug the baffling problem of how when I did a linq query, i was not part of the myCollection.

Here's some code that surprised me:

Int64 a = 12345678912345;

Console.Write((int)a);

I expected the compiler to give me an error. The usual one is that an implicit cast does not exist. But no, it didn't mind this at all. Not even a warning!

The outputted value of (int)a incidentally is 1942903641.

I'm curious to know why the cast is permitted without any warnings and also how it comes up with that value. Any ideas?

BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
NibblyPig
  • 51,118
  • 72
  • 200
  • 356

6 Answers6

15

By default, conversions like this are not checked. You'd have to ask for it explicitly:

   Console.Write(checked((int)a));    // Kaboom!

Checked conversions can be globally enabled by the C# compiler's /checked option. That the project templates do not turn this option on for the Debug build is an oversight in my book, overflows can be drastically hard to diagnose.

Nothing you can't fix however, simply use Project > Properties > Build tab > Advanced button > tick the "Check for arithmetic overflow/underflow" option. And note how your foreach loop now bombs with an OverflowException. Do keep in mind that you'll be staring it at for a couple of minutes when it happens :) It is not a very cheap check so you'll want to leave it off for the Release build.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • I have got the following **working** statement: `foreach (SecuritySystemRoleEx role in userWithRoles.Roles)` where `userWithRoles.Roles` is a `IList`. Therefore the right answer seems to be the one of @Damien_The_Unbeliever (believe it or not). – Olivier Jacot-Descombes Dec 04 '15 at 13:55
  • This Q+A is about numerical overflow checking, your foreach loop could not possible generate a OverflowException. – Hans Passant Dec 04 '15 at 14:23
  • Yes, but the overflow checking comes into play because there is an invisible cast in the foreach loop. Otherwise you would get a compiler error (Cannot implicitly convert type). The OP says: "I expected the compiler to give me an error". At least we should get a compiler warning saying that the cast is possibly unintended. – Olivier Jacot-Descombes Dec 04 '15 at 14:45
  • And I described how you can get a diagnostic for it, you ought to try it. – Hans Passant Dec 04 '15 at 14:46
6

By default, C# does not check for overflows when processing numbers. This includes things like wrapping from int.MaxValue to int.MinValue in addition and multiplication, and when you cast longs to ints. To control this, use the checked and unchecked keywords, or the /checked compiler option.

The value 1942903641 is the result when your long is truncated to an int. It comes from the 32 least significant bits of the long value, taken as a two's complement signed integer.

When using foreach, it's important to know that if you declare a type that doesn't match the type of the enumerable, it will treat it as if you casted to that type. foreach (int i in myCollection) compiles to something like int i = (int)myEnumerator.Current;, not int i = myEnumerator.Current;. You could use foreach (var i in myCollection) to avoid such mistakes in the future. var is recommended to use for the loop variable in for and foreach statements.

You can see the results of various things in the following example (hexadecimal output is used to show the truncation more clearly: they have the same ending digits, the int just lacks some of the more significant digits):

checked
{
    Int64 a = 12345678912345;
    Console.WriteLine(a.ToString("X"));
    Console.WriteLine((a % ((long)uint.MaxValue + 1L)).ToString("X"));
    try
    {
        Console.WriteLine(((int)a).ToString("X")); // throws exception
    }
    catch (Exception e)
    {
        Console.WriteLine("It threw! " + e.Message);
    }
}
unchecked
{
    Int64 a = 12345678912345;
    Console.WriteLine(a.ToString("X"));
    Console.WriteLine((a % (long)Math.Pow(2, 32)).ToString("X"));
    Console.WriteLine(((int)a).ToString("X"));
}

This outputs:

B3A73CE5B59
73CE5B59
It threw! Arithmetic operation resulted in an overflow.
B3A73CE5B59
73CE5B59
73CE5B59
Tim S.
  • 55,448
  • 7
  • 96
  • 122
  • Great answer thanks. Do you know why it's disabled by default? – NibblyPig Nov 01 '13 at 17:25
  • @SLC performance is probably the biggest reason. `checked` arithmetic is significantly slower than `unchecked`. Usually the behavior difference doesn't matter. – Tim S. Nov 01 '13 at 17:30
  • 2
    @SLC Probably for marketing reasons. See [Why don't languages raise errors on integer overflow by default?](http://stackoverflow.com/questions/103654/why-dont-languages-raise-errors-on-integer-overflow-by-default) – Justin Nov 05 '13 at 14:27
3

By default overflow checking is turned off for integral-type arithmetic operations.

You can enable it by putting your code into "checked" section:

        Int64 a = 12345678912345;
        checked
        {
            Console.Write((int)a);
        }

You can achieve the same thing by changing your compiler options.

Fayilt
  • 1,042
  • 7
  • 16
1

In modern versions of VS (since VS 2003 or so?) the arithmetic overflow/underflow check is turned off by default. You can change this in project's properties -> build -> advanced -> "Check for arithmetic overflow/underflow".

Roger
  • 1,944
  • 1
  • 11
  • 17
1

The reason the foreach hides that a cast occurs is because it originated in C# 1 - before generics.

It's defined as (Section 8.8.4 of the C# language specification v5):

A foreach statement of the form

foreach (V v in x) embedded-statement

is then expanded to:

{
    E e = ((C)(x)).GetEnumerator();
    try {
        while (e.MoveNext()) {
            V v = (V)(T)e.Current;
            embedded-statement
        }
    }
    finally {
        … // Dispose e
    }
}

As you can see, the foreach automatically gains an explicit cast on that first line.

Damien_The_Unbeliever
  • 234,701
  • 27
  • 340
  • 448
0

Your value 12345678912345 gets truncated to a 32-bit integer. It's easier to understand in in hexadecimal:

  • 12345678912345 in base 10 is b3a73ce5b59 in hexadecimal.
  • b3a73ce5b59 truncated to 32 bits is 73ce5b59 (keep the least significant 8 hexadecimal digits).
  • 73ce5b59 in hexadecimal is 1942903641 in base 10.

As to why you do not get any errors or warnings:

  • Your conversion does not involve a compile-time constant, so the compiler will not do any static checks (in this simple case, it could, but not in general).
  • By default, C#'s long to int explicit conversion generates the conv.i4 CIL instruction, which truncates the value without throwing exceptions on overflow. Using a checked statement or expression, or compiling with the /checked switch will make the C# compiler emit the conv.ovf.i4 instead, which does throw exceptions on overflow.
Trillian
  • 6,207
  • 1
  • 26
  • 36