Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does casting a NaN to a long yield a valid result?

Tags:

In the sample code below I am dividing by zero which when I step through it with the debugger the (dividend / divisor) yields an Infinity or NaN (if the divisor is zero). When I cast this result to a long I get a valid result, usually something like -9223372036854775808. Why is this cast valid? Why doesn't it stop executing (throw an exception for example) rather than assign an arbitrary value?

double divisor = 0;
double dividend = 7;
long result = (long)(dividend / divisor);
like image 433
brainimus Avatar asked Apr 14 '10 18:04

brainimus


3 Answers

Why is this cast valid?

A cast is valid if it is known at compile time that the conversion might succeed or always succeeds. Casts are only illegal when the conversion cannot possibly succeed. (For example, casting a sealed type to an interface it does not implement.) A conversion from double to long might succeed. Therefore the cast is valid.

Why doesn't it stop executing (throw an exception for example) rather than assign an arbitrary value?

Because you didn't ask for an exception! The spec is extremely clear on what the expected behaviour is. See section 6.2.1:

For a conversion from float or double to an integral type, the processing depends on the overflow checking context in which the conversion takes place:

In a checked context, the conversion proceeds as follows:

• If the value of the operand is NaN or infinite, a System.OverflowException is thrown.

[...]

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

• If the value of the operand is NaN or infinite, the result of the conversion is an unspecified value of the destination type.

You're executing the code in an unchecked context; you asked for no exception so you're getting no exception. If you want an exception, ask for one; use a checked context.

like image 57
Eric Lippert Avatar answered Jan 14 '23 03:01

Eric Lippert


By default, C# arithmetic is unchecked, so invalid operations will not throw exceptions.

You can use a checked block to force the runtime to check for overflow and throw exceptions, like this:

checked {
    double divisor = 0;
    double dividend = 7;

    long result = (long)(dividend / divisor);
}

Note that there will be a slight performance penalty.

like image 20
SLaks Avatar answered Jan 14 '23 02:01

SLaks


The behavior is explicitly documented in the C# language specification, section 6.2.1:

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

In a checked context, the conversion proceeds as follows:

  • If the value of the operand is NaN or infinite, a System.OverflowException is thrown.
  • Otherwise, the source operand 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, a System.OverflowException is thrown.

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

  • If the value of the operand is NaN or infinite, the result of the conversion is an unspecified value of the destination type.
  • Otherwise, the source operand 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.

Bold added for emphasis. You've got Infinity and an unchecked context. The value you get is unspecified. Use the checked keyword to make it bomb.

like image 26
Hans Passant Avatar answered Jan 14 '23 02:01

Hans Passant