Hai mai provato a riassumere tutti i numeri da 1 a 2.000.000 nel tuo linguaggio di programmazione preferito? Il risultato è facile da calcolare manualmente: 2.000.001.000.000, che circa 900 volte più grandi del valore massimo di un intero a 32 bit senza segno.
C # stampa -1453759936
- un valore negativo! E immagino che Java faccia lo stesso.
Ciò significa che ci sono alcuni linguaggi di programmazione comuni che ignorano l'Arithmetic Overflow per impostazione predefinita (in C #, ci sono opzioni nascoste per modificarlo). È un comportamento che mi sembra molto rischioso, e l'incidente di Ariane 5 non è stato causato da un tale trabocco?
Quindi: quali sono le decisioni di progettazione alla base di un comportamento così pericoloso?
Modificare:
Le prime risposte a questa domanda esprimono i costi eccessivi del controllo. Eseguiamo un breve programma C # per testare questo presupposto:
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);
Sul mio computer, la versione selezionata richiede 11015ms, mentre la versione non selezionata richiede 4125ms. Vale a dire i passaggi di controllo richiedono quasi il doppio dell'aggiunta dei numeri (in totale 3 volte il tempo originale). Ma con le 10.000.000.000 di ripetizioni, il tempo impiegato da un controllo è ancora inferiore a 1 nanosecondo. Potrebbe esserci una situazione in cui ciò è importante, ma per la maggior parte delle applicazioni non ha importanza.
Modifica 2:
Ho ricompilato la nostra applicazione server (un servizio Windows che analizza i dati ricevuti da diversi sensori, con alcuni crunching numerici coinvolti) con il /p:CheckForOverflowUnderflow="false"
parametro (normalmente, accendo il controllo di overflow) e l'ho distribuito su un dispositivo. Il monitoraggio di Nagios mostra che il carico medio della CPU è rimasto al 17%.
Ciò significa che l'hit performance riscontrato nell'esempio inventato sopra è totalmente irrilevante per la nostra applicazione.
(1..2_000_000).sum #=> 2000001000000
. Un altro uno dei miei preferiti lingue: sum [1 .. 2000000] --=> 2000001000000
. Non il mio preferito: Array.from({length: 2000001}, (v, k) => k).reduce((acc, el) => acc + el) //=> 2000001000000
. (Per essere onesti, l'ultimo è barare.)
Integer
in Haskell è una precisione arbitraria, conterrà qualsiasi numero finché non si esaurisce la RAM allocabile.
But with the 10,000,000,000 repetitions, the time taken by a check is still less than 1 nanosecond.
questa è un'indicazione dell'ottimizzazione del loop. Anche quella frase contraddice i numeri precedenti che mi sembrano molto validi.
checked { }
sezione per contrassegnare le parti del codice che devono eseguire i controlli di overflow aritmetico. Ciò è dovuto all'esibizione