2012-03-26 9 views
9

Qual è la prassi comune per gestire gli overflow integer come 999999 * 999999 (risultato> Integer.MAX_VALUE) da un punto di vista dello Application Development Team?Pratica comune su come gestire gli overflow integer?

Uno potrebbe semplicemente rendere BigInt obbligatorio e vietare l'uso di Integer, ma è una buona/cattiva idea?

risposta

13

Se è estremamente importante che l'intero non troppo pieno, è possibile definire le proprie operazioni di overflow-catching, ad esempio:

def +?+(i: Int, j: Int) = { 
    val ans = i.toLong + j.toLong 
    if (ans < Int.MinValue || ans > Int.MaxValue) { 
    throw new ArithmeticException("Int out of bounds") 
    } 
    ans.toInt 
} 

Si può essere in grado di utilizzare l'arricchire-your-libreria di pattern di trasformare questo negli operatori; se la JVM riesce a fare l'analisi di fuga correttamente, non sarà possibile ottenere troppo di una sanzione per esso:

class SafePlusInt(i: Int) { 
    def +?+(j: Int) = { /* as before, except without i param */ } 
} 
implicit def int_can_be_safe(i: Int) = new SafePlusInt(i) 

Ad esempio:

scala> 1000000000 +?+ 1000000000 
res0: Int = 2000000000 

scala> 2000000000 +?+ 2000000000 
java.lang.ArithmeticException: Int out of bounds 
    at SafePlusInt.$plus$qmark$plus(<console>:12) 
    ... 

Se non è estremamente importante, allora test di unità standard e revisioni di codice e simili dovrebbero cogliere il problema nella grande maggioranza dei casi. L'utilizzo di BigInt è possibile, ma rallenterà l'aritmetica di un fattore pari a 100 o giù di lì, e non ti aiuterà quando devi utilizzare un metodo esistente che prende uno Int.

+0

Ciao Rex, grazie per la pronta risposta! Una soluzione ragionevole anche se potrebbe richiedere un ri-factoring. Quanto difficile diresti è la manomissione della classe di base Integer che aggiunge il flag di controllo del flusso eccessivo alle funzioni standard di "operatore" considerando il complesso sistema di tipi? – IODEV

+0

Btw, riguardo a "" +? + "", C'è una conversione di nomi Scala che si dovrebbe usare?/Grazie in anticipo – IODEV

+0

@IODEV - È possibile aggiungere una classe wrapper, ma non è possibile alterare sensibilmente la classe base perché in realtà esegue il mapping alla primitiva 'int' nella JVM e quindi ha un sacco di magia speciale del compilatore. La scelta di '' 'era arbitaria da parte mia; iniziando con '+' manterrai la precedenza dell'operatore uguale, e mi piace la simmetria (e gli altri fanno abbastanza in modo che mentre non lo chiamo un _convention_ è almeno familiare), quindi ho aggiunto un altro '+' alla fine . '+ @' funzionerebbe altrettanto bene. –

4

Di gran lunga il più comuni pratiche in materia di integer overflow è che i programmatori sono tenuti a sapere che il problema esiste, da guardare per i casi in cui potrebbe accadere, e di fare le opportune verifiche o riorganizzare la matematica in modo che trabocca vinto Accade, cose come fare un * (b/c) piuttosto che (a * b)/c. Se il progetto utilizza il test unitario, includerà i casi per provare a forzare l'overflow.

Non ho mai lavorato o visto il codice di un team che ha richiesto più di quello, quindi ho intenzione di dire che è abbastanza buono per quasi tutti i software.

L'applicazione incorporata che ho visto che in realtà, onesto-to-spaghetti-mostro ha bisogno di evitare overflow, lo hanno fatto dimostrando che non erano possibili overflow in ogni riga in cui sembrava che potrebbero accadere.

+3

L'underflow può essere ugualmente male, che 'a * (b/c)' può darti. Dovrebbe generalmente fare '((a * b) .toLong/c) .toInt' o l'equivalente con' Double'. –

+0

Come per gli overflow, il modo per gestire i underflow è che il programmatore sia consapevole della possibilità e utilizzi il proprio JUDGMENT. Allargare gli argomenti e restringere il risultato ogni volta che si eseguono operazioni matematiche, peggiora le prestazioni e la leggibilità del codice e non funziona nei casi in cui i risultati finali sono eccessivi o insufficienti. – mjfgates

+0

Dipende dal livello di prestazioni critico del codice. Le prestazioni molto basse possono essere sfruttate con numeri interi arbitrari di precisione. Le prestazioni decenti possono andare avanti con l'allargamento/restringimento. Le alte prestazioni richiedono al programmatore di capire quali valori consentiti siano così ampi/ristretti non necessari. Le prestazioni elevatissime sarebbero le migliori se potessi evitare completamente la divisione, in qualche modo. –

6

Se stai usando Scala (e in base al tag sto assumendo sei), una soluzione molto generico è quello di scrivere il codice della libreria contro la classe scala.math.Integral tipo:

def naturals[A](implicit f: Integral[A]) = 
    Stream.iterate(f.one)(f.plus(_, f.one)) 

È inoltre possibile utilizzare limiti di contesto e Integral.Implicits per la sintassi più bello:

import scala.math.Integral.Implicits._ 

def squares[A: Integral] = naturals.map(n => n * n) 

Ora è possibile utilizzare questi metodi sia con Int o Long o BigInt, se necessario, dal momento che le istanze di Integral esiste per tutti loro:

scala> squares[Int].take(10).toList 
res0: List[Int] = List(1, 4, 9, 16, 25, 36, 49, 64, 81, 100) 

scala> squares[Long].take(10).toList 
res0: List[Long] = List(1, 4, 9, 16, 25, 36, 49, 64, 81, 100) 

scala> squares[BigInt].take(10).toList 
res1: List[BigInt] = List(1, 4, 9, 16, 25, 36, 49, 64, 81, 100) 

Non c'è bisogno di modificare il codice della libreria: basta usare Long o BigInt dove trabocco è una preoccupazione e Int altrimenti.

Pagherete alcune penali in termini di prestazioni, ma la genericità e la possibilità di rinviare la decisione -o-- BigInt potrebbe valerne la pena.

+0

usando 'Long' abbiamo di nuovo il problema di overflow. – Jus12

3

Oltre alla semplice consapevolezza, come notato da @mjfgates, ci sono un paio di pratiche che uso sempre quando si tratta di quantità reali decimali in scala (non in virgola mobile). Questo potrebbe non essere appropriato per la tua particolare applicazione - scuse in anticipo se no.

Innanzitutto, se sono presenti più unità di misura, i valori devono sempre identificare chiaramente cosa sono. Ciò può avvenire nominando una convenzione o utilizzando una classe separata per ogni unità di misura. Ho sempre usato solo nomi - un suffisso su ogni nome di variabile. Oltre ad eliminare errors from confusion over the units, incoraggia a pensare a un eccesso di dati perché le misure sono meno pensabili come semplici numeri.

In secondo luogo, la mia fonte più frequente di problemi di overflow è di solito il ridimensionamento, ovvero la conversione da una misura all'altra, quando richiede un numero elevato di cifre significative. Ad esempio, il fattore di conversione da cm a pollici è 0,393700787402. Per evitare sia l'overflow che la perdita di cifre significative, è necessario fare attenzione a moltiplicare e dividere nell'ordine corretto. Non ho fatto questo in un tempo lungo, ma credo che ciò che si vuole è qualcosa di simile:

Aggiungi Rational.scala, dal libro:

def rescale(i:Int) : Int = { 
     (i * (numer/denom)) + (i/denom * (numer % denom)) 

quindi si ottiene come risultato (accorciati da uno specs2 test):

val InchesToCm = new Rational(1000000000,393700787) 
    InchesToCm.rescale(393700787) must_== 1000000000 
    InchesToCm.rescale(1) must_== 2 

Questo non funziona, o si occupa di fattori di ridimensionamento negativi. Un'implementazione di produzione potrebbe voler calcolare numer/denom e numer % denom.

+0

+1 per legare l'unità di misura alla variabile. Avere il codice urlato contro di me se provassi a mischiare "character count" e "byte count" mi ha salvato un bacon un anno quando stavo affrontando una pila di orribili conversioni da DBCS a Unicode. – mjfgates

Problemi correlati