Ви коли-небудь намагалися підбити підсумки всіх чисел від 1 до 2 000 000 улюбленою мовою програмування? Результат легко обчислити вручну: 2 000 000 000 000, що приблизно в 900 разів перевищує максимальне значення непідписаного 32-бітного цілого числа.
C # друкує -1453759936
- негативне значення! І я думаю, що Java робить те саме.
Це означає, що є декілька поширених мов програмування, які ігнорують Arithmetic Overflow за замовчуванням (у 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 є довільною точністю, воно буде містити будь-яке число до тих пір, поки у вас не закінчиться виділена оперативна пам'ять.
But with the 10,000,000,000 repetitions, the time taken by a check is still less than 1 nanosecond.
це вказівка оптимізації циклу. Також це речення суперечить попереднім цифрам, які здаються мені дуже правильними.
checked { }
розділ для позначення частин коду, які повинні виконувати перевірки арифметичної переповнення. Це пов’язано з виставою