9

The following snippet evaluates to zero:

int result = unchecked((int)double.MaxValue);

Whereas, if you do this:

double x = double.MaxValue
int result = (int)x;

The result is (would you even guess this?) int.MinValue. That fact alone is weird enough[see below], but I was under the impression that unchecked was meant to force the compiler into emitting code that pretends not to know that a conversion will definitely fail and/or some overflow happens. In other words, it should give the same result as when the compiler has no knowledge of the values involved (assuming it is compiled with "Check for arithmetic overflow" disabled)

So, what's going on here? Is my understanding of unchecked wrong?

Is one of the results "wrong", as per the C#/.NET standard?


edit: the int.MinValue is explained easily enough: cvttsd2si gives 0x80000000 when there would have been overflow but the exception is masked. That's the instruction used by the JIT compiler, as can be seen in the disassembly window. That doesn't solve any part of the issue though.


According to ECMA 334 (C# 2 spec), the unchecked keyword should always truncate and therefore the result should be zero in both of these cases:

int result1 = unchecked((int)double.MaxValue);
double x = double.MaxValue;
int result2 = unchecked((int)x);

But it isn't, the second one gives int.MinValue. This still smells like compiler bug to me.

harold
  • 61,398
  • 6
  • 86
  • 164
  • I'm going to hazard a guess and say its something to do with the binary representations of double and int types. – Jon Grant Jul 30 '11 at 14:11
  • @Jon Grant: I doubt it, 0x80000000 is -0 as float (and not the right size for a double). However, cvttsd2si specifies that 0x80000000 is the result when the double is too big and the overflow exception is masked. So that's where `int.MinValue` comes from, but what about the zero? – harold Jul 30 '11 at 14:15
  • 3
    Just remember this, when you think it is a compiler bug, it isn't - it's your code or understanding. FYI –  Jul 30 '11 at 14:28
  • You are not looking at the real code, these expressions are evaluated by the jitter. Tools + Options, Debugging, General, untick the "Suppress JIT optimization" option. The jitter doesn't emit any code to try to normalize the overflow behavior, it is too expensive. – Hans Passant Jul 30 '11 at 15:29
  • @Hans Passant: I threw an exception in release mode and attached the debugger when it throwed – harold Jul 30 '11 at 15:40

2 Answers2

4

From MSDN on unchecked keyword,

In an unchecked context, if an expression produces a value that is outside the range of the destination type, the result is truncated.

The default context is checked,

In a checked context, if an expression produces a value that is outside the range of the destination type, the result depends on whether the expression is constant or non-constant. Constant expressions cause compile time errors, while non-constant expressions are evaluated at run time and raise exceptions.

Finally, Double/Float does not wrap.

  • int.MaxValue + 1 == int.MinValue (it overflows and wraps around with no exception)
  • uint.MaxValue + 1 == 0 (it overflows to zero since it is unsigned; no exception thrown)
  • float.MaxValue + 1 == float.MaxValue (yes, the runtime handles the overflow, no exception is thrown, but it behaves differently tha int and uint)
  • double.MaxValue + 1 == double.MaxValue (same as float)
  • decimal.MaxValue + 1 throws a System.OverflowException
  • yes, but this doesn't really explain what happened, does it? There is in fact even a different answer from `unchecked((int)double.MaxValue)` and `unchecked((int)MaxValueOfDouble)` where `MaxValueOfDouble` is a variable set to `double.MaxValue`. – harold Jul 30 '11 at 14:29
  • @harold: Double.MaxValue is larger than what an int can hold. –  Jul 30 '11 at 14:30
  • Yes, clearly, and I have no problem with the result being either int.MinValue or zero, what bugs me is that the result is different depending on the circumstances. – harold Jul 30 '11 at 14:31
  • @harold: If you are going to use the unchecked keyword, "the result is truncated" - possibly to 0. If it is unchecked, it should tell you that it is outside the range of int. –  Jul 30 '11 at 14:35
  • So I can conclude from this the the unchecked keywords behaves differently for constant expression and variable expressions. Nowhere that I can find does it say that's supposed the be the behaviour. – harold Jul 30 '11 at 14:39
4

Great, I found it. Buried deep inside the spec, there is a the following:

In an unchecked context, the conversion always succeeds, and proceeds as follows.

• The value is rounded towards zero to the nearest integral value. If this integral value is within the range of the destination type, then this value is the result of the conversion.

Otherwise, the result of the conversion is an unspecified value of the destination type.

So that's it. The result is undefined. Everything goes.

Community
  • 1
  • 1
harold
  • 61,398
  • 6
  • 86
  • 164