曾经尝试用您喜欢的编程语言对1到2,000,000之间的所有数字求和吗?结果很容易手动计算:2,000,001,000,000,比无符号32位整数的最大值大900倍。
C#打印出来-1453759936
-负值!而且我猜Java也是如此。
这意味着有一些通用的编程语言默认情况下会忽略算术溢出(在C#中,存在用于更改该值的隐藏选项)。在我看来,这是一种非常冒险的行为,难道不是由这样的溢出导致Ariane 5崩溃吗?
那么:如此危险的行为背后的设计决策是什么?
编辑:
这个问题的第一个答案表示检查的过度成本。让我们执行一个简短的C#程序来测试此假设:
Stopwatch watch = Stopwatch.StartNew();
checked
{
for (int i = 0; i < 200000; i++)
{
int sum = 0;
for (int j = 1; j < 50000; j++)
{
sum += j;
}
}
}
watch.Stop();
Console.WriteLine(watch.Elapsed.TotalMilliseconds);
在我的计算机上,选中的版本需要11015毫秒,而未选中的版本需要4125毫秒。即检查步骤几乎花了两倍的时间(总共是原始时间的三倍)。但是,有了10,000,000,000次重复,检查所花费的时间仍不到1纳秒。在某些情况下,这很重要,但是对于大多数应用程序而言,这并不重要。
编辑2:
我使用/p:CheckForOverflowUnderflow="false"
参数(通常,我打开了溢出检查功能)重新编译了服务器应用程序(Windows服务,该服务分析从多个传感器接收的数据,涉及很多数字运算),并将其部署在设备上。Nagios监控显示,平均CPU负载保持在17%。
这意味着在上面的虚构示例中发现的性能下降与我们的应用完全无关。
(1..2_000_000).sum #=> 2000001000000
。我最喜欢的另一种语言:sum [1 .. 2000000] --=> 2000001000000
。不是我的最爱:Array.from({length: 2000001}, (v, k) => k).reduce((acc, el) => acc + el) //=> 2000001000000
。(公平地说,最后一个是作弊。)
Integer
Haskell中的@BernhardHiller 是任意精度的,只要您没有用完可分配的RAM,它将保存任何数字。
But with the 10,000,000,000 repetitions, the time taken by a check is still less than 1 nanosecond.
这表明循环已被优化。同样,这句话与以前的数字相矛盾,后者对我来说非常有效。
checked { }
section标记应执行算术溢出检查的代码部分。这是由于性能