为什么这个检查计算不会抛出OverflowException?

时间:2014-09-01 12:12:16

标签: c#

有人可以解释以下行为:

    static void Main(string[] args)
    {
        checked
        {
            double d = -1d + long.MinValue; //this resolves at runtime to -9223372036854780000.00
            //long obviousOverflow = -9223372036854780000; //compile time error, '-' cannot be applied to operand of tpye ulong -> this makes it obvious that -9223372036854780000 overflows a long.
            double one = 1;
            long lMax = (long)(one + long.MaxValue); // THROWS
            long lMin = (long)(-one  + long.MinValue); // THEN WHY DOES THIS NOT THROW?
        }
    }

我不能忽视为什么我没有在最后一行代码中获得OverFlowException

UPDATE 更新了代码,以便明确检查在将double转换为long时执行,除非在最后一种情况下。

3 个答案:

答案 0 :(得分:1)

您使用double值(-1d)进行计算。浮点数不会抛出.NET。 checked对他们没有任何影响。

但转换回long的转换受checked的影响。 one + long.MaxValue不符合double的范围。 -one + long.MinValue确实符合该范围。原因是有符号整数的负数多于正数。 long.MinValue没有正当等价物。这就是为什么你的代码的负面版本恰好适合而正面版本不合适的原因。

添加操作不会改变任何内容:

Debug.Assert((double)(1d + long.MaxValue) == (double)(0d + long.MaxValue));
Debug.Assert((double)(-1d + long.MinValue) == (double)(-0d + long.MinValue));

我们计算的数字超出了double精确的范围。 double可以精确地拟合最大为2 ^ 53的整数。我们这里有舍入错误。添加一个与添加零相同。从本质上讲,你是在计算:

var min = (long)(double)(long.MinValue); //does not overflow
var max = (long)(double)(long.MaxValue); //overflows (compiler error)

添加操作是红鲱鱼。它不会改变任何东西。

答案 1 :(得分:0)

显然,从双倍到长度的转换有一些余地。如果我们运行以下代码:

checked {
    double longMinValue = long.MinValue;

    var i = 0;
    while (true)
    {
        long test = (long)(longMinValue - i);
        Console.WriteLine("Works for " + i++.ToString() + " => " + test.ToString());
    }
}

在使用Works for 1024 => -9223372036854775808失败之前,它会升至OverflowException,且-9223372036854775808值永远不会因i而改变。

如果我们未选中运行代码,则不会抛出任何异常。

此行为与documentation on explicit numeric conversions的说法不一致:

  

当你从double或float值转换为整数类型时,   值被截断。如果得到的积分值在...之外   目标值的范围,结果取决于溢出   检查上下文。在已检查的上下文中,出现OverflowException   抛出,而在未经检查的上下文中,结果是未指定的   目的地类型的值。

但是如示例所示,截断不会立即发生。

答案 2 :(得分:0)

我认为这是一个问题:

  

我没有说明为什么我最后没有得到OverFlowException   代码行。

看看这一行(你使用的1d是无关紧要的,可以删除,它提供的唯一内容就是转换为double):

var max = (long)(double)long.MaxValue;

因为最接近(我不知道规范,所以我不会进入“最接近的”这里),因为int64.MaxValue的双重表示大于最大int64,因此抛出它无法转换回来。

var min = (long)(double)long.MinValue;

另一方面,对于这一行,最接近的双重表示在int64.MinValue和0之间,因此可以将其转换回int64

我刚才所说的并不适用于所有抖动,硬件等组合,但我试图解释会发生什么。请记住,在您的情况下,由于checked关键字而被抛出,没有它,抖动就会吞下它。

另外,我建议您查看BitConverter.GetBytes()来试验当您从double转到long然后返回大号时会发生什么,decimaldouble很有意思:)(字节表示是唯一可以信任的表示顺便说一句,在double时不要使用调试器来提高精度)