Вы когда-нибудь пытались суммировать все числа от 1 до 2 000 000 на вашем любимом языке программирования? Результат легко вычислить вручную: 2 000 001 000 000, что примерно в 900 раз превышает максимальное значение 32-разрядного целого числа без знака.
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мс. Т.е. шаги проверки занимают почти вдвое больше времени, чем сложение чисел (всего в 3 раза больше исходного времени). Но с 10 000 000 000 повторений время, затрачиваемое на проверку, все равно составляет менее 1 наносекунды. Может быть ситуация, когда это важно, но для большинства приложений это не имеет значения.
Изменить 2:
Я перекомпилировал наше серверное приложение (служба Windows, анализирующая данные, полученные от нескольких датчиков, с некоторым перебором чисел) с /p:CheckForOverflowUnderflow="false"
параметром (обычно я включаю проверку переполнения) и развернул его на устройстве. Мониторинг Nagios показывает, что средняя загрузка процессора осталась на уровне 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 имеет произвольную точность, он будет содержать любое число до тех пор, пока у вас не закончится выделенная RAM.
But with the 10,000,000,000 repetitions, the time taken by a check is still less than 1 nanosecond.
это признак оптимизации цикла. Также это предложение противоречит предыдущим числам, которые кажутся мне очень важными.
checked { }
раздел, чтобы отметить части кода, которые должны выполнять проверки арифметического переполнения. Это связано с производительностью