Why int.MaxValue - int.MinValue = -1?

35

据我所知,这会导致溢出错误,当我像这样编写时:

public static void Main()
{

    Console.WriteLine(int.MaxValue - int.MinValue);
}

它确实给了我一个溢出错误。

然而:

public static void Main()
{

    Console.WriteLine(test());
}

public static Int32 test(int minimum = int.MinValue, int maximum = int.MaxValue)
{
    return maximum - minimum;
}

将输出-1。为什么会这样?它应该抛出一个错误,因为这明显是一个溢出!


8
我是唯一一个被教过2147483647-(-2147483648)实际上是4294967295的人吗? - Aelphaeis
1
4294967295不是一个整数。 - harold
1
你认为2147483647 - 2147483648会得出什么结果? - Michael McGriff
@MichaelMcGriff,你的数学有误。int.MinValue是负数。减去一个负值实际上是加法。 - Erik Philips
1
@MichaelMcGriff,那么这与问题无关。这个问题是“最大值-最小值”,或者使用默认值int.MaxValue - int.MinValue2147483647 - -2147483648。我意识到仅仅移除其中一个修饰符会返回-1,但这是因为CLR是这样编码的,而不是因为某种自然数学规律。 - Erik Philips
显示剩余2条评论
4个回答

55

int.MaxValue - int.MinValue = 一个int类型无法容纳的值。因此,这个数字会被重新包装回-1。

就像2147483647-(-2147483648)= 4294967295,这也不是一个int类型的值

Int32.MinValue Field

这个常量的值为-2,147,483,648;,即十六进制 0x80000000。

还有Int32.MaxValue Field

这个常量的值为2,147,483,647;,即十六进制 0x7FFFFFFF。

来自MSDN

当整数溢出发生时,发生什么取决于执行上下文,可以进行检查或不检查。在检查的上下文中,会抛出一个OverflowException。在未检查的上下文中,结果的最高有效位会被丢弃,执行继续。因此,C#让您选择处理或忽略溢出。


4
编辑解释了它,我觉得默认情况下编译器处于未检查状态很令人惊讶。 - Aelphaeis
@Aelphaeis:是的,此外,checked关键字用于显式启用整数类型算术运算和转换的溢出检查。 - user3414693
2
@Aelphaeis 我也很惊讶,因为我确信默认情况下已经勾选了。但是根据MSDN的说法,的确/checked-是默认设置。这似乎是一个糟糕的默认选择,因为它允许在运行时忽略编程错误(可能会有安全隐患?)。我想知道C#团队为什么会做出这样的选择... - LB2
2
C#标准规定unchecked为默认值,但我意识到这并没有真正解释什么,这只是把问题转化为“那么为什么C#标准要这样做”。也许是出于性能考虑,但谁又真正知道呢? - harold
1
通常这些事情都是为了提高性能而做的。这样,市场部门就可以展示漂亮的图表,显示启用默认设置和优化后C#与Java的性能对比。从程序员的角度来看,最好默认启用检查,当检查出错时使用良好的异常处理,并且只有在软件变得稳定之后才启用不安全模式。但请记住,在典型的数字基准测试中,启用检查模式可能会相当慢,因为它很容易将整数操作的数量增加三倍。 - Michel Müller

15

这是由于编译时对您的代码进行了溢出检查。该行

Console.WriteLine(int.MaxValue - int.MinValue);

实际上不会在运行时出现错误,它只会简单地写入“-1”,但由于溢出检查,您会得到编译错误:“在已检查的模式下,操作在编译时溢出”。

要解决此情况下的编译时溢出检查问题,您可以执行以下操作:

         unchecked
        {
            Console.WriteLine(int.MaxValue - int.MinValue);                
        }

这将正常运行并输出“-1”


向 @Michael 喊话,强调运行时溢出检查是编译级别的一个选项。 - Aidanapword
1
为了使这个答案更完整,您还可以展示相反的效果 - 在test方法中将计算放在checked中 - 以使其抛出异常,这正是OP想要的。 - ClickRick

2

默认情况下,控制此设置的项目级别设置为默认状态下的“未选中”。您可以通过转到项目属性,构建选项卡,高级按钮来打开溢出检查。弹出窗口允许您打开溢出检查。您链接到的.NET Fiddle工具似乎执行了一些额外的静态分析,这会防止您看到真正的开箱即用运行时行为。(上面您第一个代码片段的错误是“在已检查模式下,操作在编译时溢出。” 您没有看到运行时错误。)


2
我认为这甚至比溢出更深入。
如果我看这个
 Int64 max = Int32.MaxValue;
 Console.WriteLine(max.ToString("X16")); // 000000007FFFFFFF
 Int64 min = Int32.MinValue;
 Console.WriteLine(min.ToString("X")); //FFFFFFFF80000000
 Int64 subtract = max - min;
 Console.WriteLine(subtract.ToString("X16")); //00000000FFFFFFFF <- not an overflow since it's a 64 bit number
 Int32 neg = -1
 Console.WriteLine(neg.ToString("X")); //FFFFFFFF

这里可以看到,如果你在2的补码中仅仅减去十六进制值,你就能得到32位数中的-1。(在截断前导0之后)

2的补码算术运算可以非常有趣。http://en.wikipedia.org/wiki/Two's_complement


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接