Double.MaxValue到整数是否定的?
为什么Double.MaxValue
转换为整数类型会导致负值,该类型的最小值?
double maxDouble = double.MaxValue; // 1.7976931348623157E+308 long maxDoubleLong = (long) maxDouble; // -9223372036854775808
我理解编译器错误,如果它太大或运行时出现OverflowException
,或者如果我使用unchecked
,转换可能不会引发exception,但结果将变为未定义且不正确(否定)。
同样奇怪的是值很长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 }
C#语言规范(版本5.0)在6.2.1“显式数字转换”(重点添加)中说明以下内容:
对于从float或double到整数类型的转换,处理取决于转换发生的溢出检查上下文(第7.6.12节):
在已检查的上下文中,转换过程如下:
- 如果操作数的值为NaN或无穷大,则抛出System.OverflowException。
- 否则,源操作数向零舍入为最接近的整数值。 如果此整数值在目标类型的范围内,则此值是转换的结果。
- 否则,抛出System.OverflowException。
在未经检查的上下文中,转换始终成功,并按如下方式继续。
- 如果操作数的值为NaN或无限,则转换的结果是目标类型的未指定值。
- 否则,源操作数向零舍入为最接近的整数值。 如果此整数值在目标类型的范围内,则此值是转换的结果。
- 否则,转换的结果是目标类型的未指定值。
并在7.6.12“已检查和未检查的运算符”
对于未由任何已检查或未检查的运算符或语句包含的非常量表达式(在运行时计算的表达式),除非外部因素(如编译器开关和执行环境配置)调用,否则将取消选中默认溢出检查上下文检查评估。
对于从double
到decimal
转换:“如果源值为NaN,无穷大或太大而无法表示为十进制,则抛出System.OverflowException”。 checked
vs unchecked
不会发挥作用(仅处理整体操作)。
也许不是一个完整的答案,但C#语言规范(§6.2.1)说:
在未经检查的上下文中,转换始终成功,并按如下方式继续。
•如果操作数的值为NaN或无穷大,则转换结果为目标类型的未指定值。
•否则,源操作数将向零舍入为最接近的整数值。 如果此整数值在目标类型的范围内,则此值是转换的结果。
•否则,转换结果是目标类型的未指定值 。
(强调我的)。
(Michael Burr与我同时回答,他还在C#中包含了关于默认checked
/ unchecked
上下文的信息,参见下面的评论,所以这个答案现在基本上是多余的。)
编辑1:请注意,如果转换完成编译时 (常量表达式转换),则规则略有不同。 尝试使用const
修饰符修改maxDouble
变量。 然后,C#编译器将能够看到这些值,并且它将要求您明确unchecked
。
编辑2:在我的运行时版本(.NET 4.5 for 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
。
看来这里的默认行为是unchecked
,即除非您明确指定checked
,否则溢出不会被检测到:
double maxDouble = double.MaxValue; // 1.7976931348623157E+308 long uncheckedMaxDoubleLong = (long)maxDouble; // -9223372036854775808 long checkedMaxDoubleLong = checked((long)maxDouble); // ** Overflow Exception
事后看来,由于以下两个方面,尝试直接从double
转换为long
而不首先validation或约束输入是不明智的:
- 数值范围不匹配/溢出的可能性
- 舍入考虑因素
所以,这里更好的选择可能是使用Convert.ToInt64
:
var convertedDouble = Convert.ToInt64(maxDouble); // ** OverflowException
因为这在内部对你checked
检查,并对四舍五入进行了评论,即:
return checked((long)Math.Round(value));