Double.MaxValue to integer is negative?
Asked Answered
M

3

24

Why does Double.MaxValue casted to an integral type results in a negative value, the smallest value of that type?

double maxDouble = double.MaxValue;       // 1.7976931348623157E+308
long maxDoubleLong = (long) maxDouble;    // -9223372036854775808

I'd understand a compiler error if it's too large or an OverflowException at runtime or if i'd use unchecked that the conversion might not throw an exception, but the result becomes undefined and incorrect(negative).

Also strange is that the value is long.MinValue:

bool sameAsLongMin = maxDoubleLong == long.MinValue; // true

By the way, the same happens if i cast it to int:

int maxDoubleInt = (int)maxDouble;                   // -2147483648
bool sameAsIntMin = maxDoubleInt == int.MinValue;    // true

If it try to cast it to decimal i get an OverflowException at runtime

decimal maxDoubleDec = (decimal)maxDouble;  // nope

Update: it seems that Michael's and Barre's answers hit the nail on the head, if i use checked explicitly i get an OverflowException:

checked
{
    double maxDouble = double.MaxValue;     // 1.7976931348623157E+308
    long maxDoubleLong = (long) maxDouble;  // nope
}
Mussorgsky answered 31/3, 2014 at 8:48 Comment(1)
You are missing a minus sign in the code comment following the line with maxDoubleInt.Confluent
B
16

The C# Language Specification (Version 5.0) says the following in 6.2.1 "Explicit numeric conversions" (emphasis added):

  • For a conversion from float or double to an integral type, the processing depends on the overflow checking context (§7.6.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.

And in 7.6.12 "The checked and unchecked operators"

For non-constant expressions (expressions that are evaluated at run-time) that are not enclosed by any checked or unchecked operators or statements, the default overflow checking context is unchecked unless external factors (such as compiler switches and execution environment configuration) call for checked evaluation.

For conversions from double to decimal: "If the source value is NaN, infinity, or too large to represent as a decimal, a System.OverflowException is thrown". checked vs unchecked doesn't come into play (those deal with integral operations only).

Boardman answered 31/3, 2014 at 8:59 Comment(3)
Yes, I checked the IL and it uses conv.i8, the documentation for which also explicitly states "If overflow occurs converting a floating-point type to an integer the value returned is unspecified."Les
Yes, you were right, if i use checked explicitly i get an OverflowException. It is a weird behaviour, isn't it?Mussorgsky
@TimSchmelter: I'm guessing that they chose to use long.MinValue to help errors become more evident; a manifestation of the 'fail fast' philosophy. If you think about it, long.MaxValue really isn't any more correct, but it might make further use of the value less obviously wrong.Boardman
C
8

Maybe not a full answer, but the C# Language Specification (§6.2.1) says this:

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.

(emphasis mine).

(Michael Burr answered at the same time as me, and he also included info on the default checked/unchecked context in C#, cf. comments below, so this answer is largely redundant now.)

Edit 1: Note that if the conversion is done compile-time (constant expression conversion), rules are a bit different. Try modifying your maxDouble variable with the const modifier. The C# compiler will then be able to see the values, and it will require you to be explicit about the unchecked.

Edit 2: In my version of the runtime (.NET 4.5 for Windows 8.1), the following code:

double d1 = double.PositiveInfinity;
double d2 = double.MaxValue;
double d3 = 2.3e23;
double d4 = double.NaN;
double d5 = -2.3e23;
double d6 = double.MinValue;
double d7 = double.NegativeInfinity;
Console.WriteLine((long)d1);
Console.WriteLine((long)d2);
Console.WriteLine((long)d3);
Console.WriteLine((long)d4);
Console.WriteLine((long)d5);
Console.WriteLine((long)d6);
Console.WriteLine((long)d7);

gives:

-9223372036854775808
-9223372036854775808
-9223372036854775808
-9223372036854775808
-9223372036854775808
-9223372036854775808
-9223372036854775808

so it appears that the "unspecified value" is in fact "always" MinValue of the destination type, in this implementation.

Confluent answered 31/3, 2014 at 8:59 Comment(4)
I have not used unchecked.Mussorgsky
@TimSchmelter Have you used checked then? The default would be unchecked and that would be the context you have, unless you either (1) specified checked in the code, or (2) set a property in your C# project file to get everything checked, or (3) used a command-line option to the csc.exe compiler.Confluent
No, i have created an empty console application with default settings and without checked or unchecked.Mussorgsky
@TimSchmelter By default checked arithmetic is off, you can enable it in the project settings. I recommend enabling it, it costs some performance but saves debugging time and might even prevent a security hole in some rare cases.Rifle
G
2

It seems the default behaviour here is unchecked, viz that unless you explicitly specify checked, the overflow goes undetected:

 double maxDouble = double.MaxValue;       // 1.7976931348623157E+308
 long uncheckedMaxDoubleLong = (long)maxDouble;    // -9223372036854775808
 long checkedMaxDoubleLong = checked((long)maxDouble); // ** Overflow Exception

In hindsight, attempting direct conversion from double to long without validating or constraining the input first is ill advised due to 2 aspects:

  • numeric range mismatches / potential for overflow
  • rounding considerations

So, a better bet here may have been to use Convert.ToInt64:

 var convertedDouble = Convert.ToInt64(maxDouble);    // ** OverflowException

As this internally does the checked checking for you, and takes an opinion on rounding, viz:

 return checked((long)Math.Round(value));
Genni answered 31/3, 2014 at 9:3 Comment(1)
If I were designing a language, I would require that all type conversions from floating-point to integer types be done via methods rather than typecasts; rather than having a language designer try to choose the one set of semantics that people will need most often, it would be more helpful to allow programmers to say whether they'd rather have fractional values trapped, lopped off, or rounded, whether negative-number behavior should be handled periodic or symmetric, and whether they'd like out-of-range values pegged, trapped, or "whatever's convenient".Imperception

© 2022 - 2024 — McMap. All rights reserved.