Double.MaxValue для целого является отрицательным?
Почему Double.MaxValue
, приведённый к интегральному типу, приводит к отрицательному значению, наименьшему значению этого типа?
double maxDouble = double.MaxValue; // 1.7976931348623157E+308
long maxDoubleLong = (long) maxDouble; // -9223372036854775808
Я бы понял ошибку компилятора, если он слишком большой или OverflowException
во время выполнения, или если я буду использовать unchecked
, что преобразование может не вызывать исключение, но результат становится undefined и неверным (отрицательный).
Также странно, что значение long.MinValue
:
bool sameAsLongMin = maxDoubleLong == long.MinValue; // true
Кстати, то же самое происходит, если я отбрасываю его на int
:
int maxDoubleInt = (int)maxDouble; // -2147483648
bool sameAsIntMin = maxDoubleInt == int.MinValue; // true
Если он пытается передать его в decimal
, я получаю OverflowException
во время выполнения
decimal maxDoubleDec = (decimal)maxDouble; // nope
Обновить: кажется, что ответы Майкла и Барра ударяют ноготь по голове, если я использую checked
явно, я получаю OverflowException
:
checked
{
double maxDouble = double.MaxValue; // 1.7976931348623157E+308
long maxDoubleLong = (long) maxDouble; // nope
}
Ответы
Ответ 1
Спецификация языка С# (версия 5.0) гласит следующее в 6.2.1 "Явные числовые преобразования" (выделено мной):
И в 7.6.12 "Проверенные и непроверенные операторы"
Для не константных выражений (выражения, которые оцениваются в время выполнения), которые не включены никакими проверенными или непроверенными операторами или операторов, контекст проверки переполнения по умолчанию не установленесли внешние факторы (такие как коммутаторы и выполнение компилятора конфигурация окружения). Вызовите проверяемую оценку.
Для конверсий от double
до decimal
: "Если исходное значение NaN, бесконечность или слишком большое, чтобы представлять в виде десятичного числа, генерируется исключение System.OverflowException". checked
vs unchecked
не вступает в игру (только для интегральных операций).
Ответ 2
Возможно, не полный ответ, но спецификация языка С# (§6.2.1) говорит следующее:
В неконтролируемом контексте преобразование всегда выполняется успешно, и поступает следующим образом.
• Если значение операнда NaN или бесконечно, результатом преобразования является неуказанное значение тип назначения.
• В противном случае исходный операнд округляется в направлении от нуля до ближайшего целочисленного значения. Если это целочисленное значение находится внутри диапазон типа назначения, то это значение является результатом преобразование.
• В противном случае результат преобразования неопределенное значение для типа адресата.
(основное внимание).
(Майкл Берр ответил в то же время, что и я, и он также включил информацию о стандартном контексте checked
/unchecked
в С#, см. комментарии ниже, поэтому этот ответ в значительной степени избыточен.)
Изменить 1: Обратите внимание, что если преобразование выполнено время компиляции (преобразование постоянного выражения), правила немного разные. Попробуйте изменить переменную maxDouble
с помощью модификатора const
. Затем компилятор С# сможет видеть значения, и для этого потребуется явно указать unchecked
.
Изменить 2: В моей версии среды выполнения (.NET 4.5 для Windows 8.1) следующий код:
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);
дает:
-9223372036854775808
-9223372036854775808
-9223372036854775808
-9223372036854775808
-9223372036854775808
-9223372036854775808
-9223372036854775808
поэтому оказывается, что "неуказанное значение" на самом деле "всегда" MinValue
для типа назначения в этой реализации.
Ответ 3
Кажется, что поведение по умолчанию здесь unchecked
, а именно: если вы явно не указали checked
, переполнение не будет обнаружено:
double maxDouble = double.MaxValue; // 1.7976931348623157E+308
long uncheckedMaxDoubleLong = (long)maxDouble; // -9223372036854775808
long checkedMaxDoubleLong = checked((long)maxDouble); // ** Overflow Exception
В ретроспективе попытка прямого преобразования от double
до long
без проверки или ограничения ввода сначала не рекомендуется из-за двух аспектов:
- числовые диапазоны несоответствий/потенциал для переполнения
- соображения округления
Итак, лучше было бы здесь использовать Convert.ToInt64
:
var convertedDouble = Convert.ToInt64(maxDouble); // ** OverflowException
Поскольку это внутренне проверяет вам checked
и высказывает мнение о округлении, а именно:
return checked((long)Math.Round(value));