Voglio garantire che una divisione di numeri interi sia sempre arrotondata per eccesso, se necessario. C'è un modo migliore di questo? C'è un sacco di casting in corso. :-)
(int)Math.Ceiling((double)myInt1 / myInt2)
Voglio garantire che una divisione di numeri interi sia sempre arrotondata per eccesso, se necessario. C'è un modo migliore di questo? C'è un sacco di casting in corso. :-)
(int)Math.Ceiling((double)myInt1 / myInt2)
Risposte:
AGGIORNAMENTO: Questa domanda è stata l'oggetto del mio blog a gennaio 2013 . Grazie per l'ottima domanda!
Ottenere l'aritmetica intera corretta è difficile. Come è stato ampiamente dimostrato finora, nel momento in cui provi a fare un trucco "intelligente", le probabilità sono buone che tu abbia commesso un errore. E quando viene trovato un difetto, cambiare il codice per correggere l'errore senza considerare se la correzione rompe qualcos'altro non è una buona tecnica di risoluzione dei problemi. Finora abbiamo pensato a cinque diverse soluzioni aritmetiche intere errate a questo problema completamente non particolarmente difficile pubblicato.
Il modo giusto di affrontare i problemi aritmetici interi, ovvero il modo in cui aumenta la probabilità di ottenere la risposta giusta la prima volta, è quello di affrontare il problema con attenzione, risolverlo un passo alla volta e utilizzare buoni principi ingegneristici nel fare così.
Inizia leggendo le specifiche per ciò che stai cercando di sostituire. Le specifiche per la divisione dei numeri interi indicano chiaramente:
La divisione arrotonda il risultato allo zero
Il risultato è zero o positivo quando i due operandi hanno lo stesso segno e zero o negativo quando i due operandi hanno segni opposti
Se l'operando di sinistra è l'int più piccolo rappresentabile e l'operando di destra è -1, si verifica un overflow. [...] è definito dall'implementazione se viene lanciata [un'eccezione aritmetica] o se l'overflow non viene segnalato con il valore risultante quello dell'operando di sinistra.
Se il valore dell'operando destro è zero, viene generata una System.DivideByZeroException.
Ciò che vogliamo è una funzione di divisione di numeri interi che calcola il quoziente ma arrotonda il risultato sempre verso l'alto , non sempre verso zero .
Quindi scrivi una specifica per quella funzione. La nostra funzione int DivRoundUp(int dividend, int divisor)deve avere un comportamento definito per ogni possibile input. Quel comportamento indefinito è profondamente preoccupante, quindi eliminiamolo. Diremo che la nostra operazione ha questa specifica:
l'operazione viene generata se il divisore è zero
l'operazione viene generata se il dividendo è int.minval e il divisore è -1
se non c'è resto - la divisione è 'pari' - allora il valore di ritorno è il quoziente integrale
Altrimenti restituisce il numero intero più piccolo che è maggiore del quoziente, ovvero viene sempre arrotondato per eccesso.
Ora abbiamo una specifica, quindi sappiamo che possiamo elaborare un design verificabile . Supponiamo di aggiungere un ulteriore criterio di progettazione secondo cui il problema deve essere risolto esclusivamente con l'aritmetica dei numeri interi, anziché calcolare il quoziente come doppio, poiché la soluzione "doppia" è stata esplicitamente respinta nella dichiarazione del problema.
Quindi cosa dobbiamo calcolare? Chiaramente, per soddisfare le nostre specifiche pur rimanendo esclusivamente nell'aritmetica dei numeri interi, dobbiamo conoscere tre fatti. Innanzitutto, qual era il quoziente intero? In secondo luogo, la divisione era libera dal resto? E terzo, in caso contrario, il quoziente intero è stato calcolato arrotondando per eccesso o per difetto?
Ora che abbiamo una specifica e un design, possiamo iniziare a scrivere codice.
public static int DivRoundUp(int dividend, int divisor)
{
if (divisor == 0 ) throw ...
if (divisor == -1 && dividend == Int32.MinValue) throw ...
int roundedTowardsZeroQuotient = dividend / divisor;
bool dividedEvenly = (dividend % divisor) == 0;
if (dividedEvenly)
return roundedTowardsZeroQuotient;
// At this point we know that divisor was not zero
// (because we would have thrown) and we know that
// dividend was not zero (because there would have been no remainder)
// Therefore both are non-zero. Either they are of the same sign,
// or opposite signs. If they're of opposite sign then we rounded
// UP towards zero so we're done. If they're of the same sign then
// we rounded DOWN towards zero, so we need to add one.
bool wasRoundedDown = ((divisor > 0) == (dividend > 0));
if (wasRoundedDown)
return roundedTowardsZeroQuotient + 1;
else
return roundedTowardsZeroQuotient;
}
È intelligente? No. Bello? No. Breve? No. Corretto secondo le specifiche? Credo di si, ma non l'ho ancora testato. Sembra abbastanza buono però.
Siamo professionisti qui; usare buone pratiche ingegneristiche. Cerca i tuoi strumenti, specifica il comportamento desiderato, considera prima i casi di errore e scrivi il codice per enfatizzare la sua ovvia correttezza. E quando trovi un bug, considera se il tuo algoritmo è profondamente imperfetto prima di iniziare a scambiare casualmente le direzioni dei confronti e rompere cose che già funzionano.
Tutte le risposte qui finora sembrano piuttosto complicate.
In C # e Java, per dividendi e divisori positivi, devi semplicemente fare:
( dividend + divisor - 1 ) / divisor
((13-1)%3)+1)dà 1 come risultato. Prendendo il giusto tipo di divisione, si 1+(dividend - 1)/divisorottiene lo stesso risultato della risposta per dividendi e divisori positivi. Inoltre, nessun problema di trabocco, per quanto artificiale possa essere.
Per numeri interi con segno:
int div = a / b;
if (((a ^ b) >= 0) && (a % b != 0))
div++;
Per numeri interi senza segno:
int div = a / b;
if (a % b != 0)
div++;
La divisione intera ' /' è definita per arrotondare verso zero (7.7.2 della specifica), ma vogliamo arrotondare per eccesso. Ciò significa che le risposte negative sono già arrotondate correttamente, ma le risposte positive devono essere adattate.
Le risposte positive diverse da zero sono facili da rilevare, ma la risposta zero è un po 'più complicata, dal momento che può essere l'arrotondamento di un valore negativo o l'arrotondamento di uno positivo.
La scommessa più sicura è rilevare quando la risposta dovrebbe essere positiva verificando che i segni di entrambi i numeri siano identici. L'operatore xo intero ' ^' sui due valori genererà un bit di segno 0 in questo caso, il che significa un risultato non negativo, quindi il controllo (a ^ b) >= 0determina che il risultato avrebbe dovuto essere positivo prima dell'arrotondamento. Si noti inoltre che per numeri interi senza segno, ogni risposta è ovviamente positiva, quindi questo controllo può essere omesso.
L'unico controllo rimanente è quindi se si è verificato un arrotondamento, per il quale a % b != 0farà il lavoro.
L'aritmetica (numero intero o altro) non è così semplice come sembra. Pensare attentamente richiesto in ogni momento.
Inoltre, sebbene la mia risposta finale non sia forse "semplice" o "ovvio" o forse "veloce" come risponde il virgola mobile, ha una qualità di riscatto molto forte per me; Ora ho ragionato sulla risposta, quindi in realtà sono sicuro che sia corretta (fino a quando qualcuno più intelligente non mi dirà diversamente - sguardo furtivo nella direzione di Eric -).
Per avere la stessa sensazione di certezza sulla risposta in virgola mobile, dovrei fare di più (e forse più complicato) pensando se ci sono condizioni in cui la precisione in virgola mobile potrebbe interferire e se Math.Ceilingforse lo fa qualcosa di indesiderabile sugli input "giusti".
Sostituisci (nota che ho sostituito il secondo myInt1con myInt2, supponendo che fosse quello che volevi dire):
(int)Math.Ceiling((double)myInt1 / myInt2)
con:
(myInt1 - 1 + myInt2) / myInt2
L'unica avvertenza è che se myInt1 - 1 + myInt2trabocca il tipo intero che stai usando, potresti non ottenere quello che ti aspetti.
Motivo per cui è sbagliato : -1000000 e 3999 dovrebbero dare -250, questo dà -249
EDIT:
considerando che questo ha lo stesso errore dell'altra soluzione intera per myInt1valori negativi , potrebbe essere più facile fare qualcosa del tipo:
int rem;
int div = Math.DivRem(myInt1, myInt2, out rem);
if (rem > 0)
div++;
Questo dovrebbe dare il risultato corretto divutilizzando solo operazioni interi.
Motivo per cui è sbagliato : -1 e -5 dovrebbero dare 1, questo dà 0
EDIT (ancora una volta, con sentimento):
l'operatore di divisione arrotonda verso zero; per risultati negativi questo è esattamente giusto, quindi solo i risultati non negativi devono essere adattati. Considerando anche che DivRemfa solo una /e una %, saltiamo la chiamata (e iniziamo con il confronto semplice per evitare il calcolo del modulo quando non è necessario):
int div = myInt1 / myInt2;
if ((div >= 0) && (myInt1 % myInt2 != 0))
div++;
Motivo per cui è sbagliato : -1 e 5 dovrebbero dare 0, questo da 1
(A mia difesa dell'ultimo tentativo non avrei mai dovuto tentare una risposta motivata mentre la mia mente mi stava dicendo che ero in ritardo di 2 ore per dormire)
Perfetta possibilità di utilizzare un metodo di estensione:
public static class Int32Methods
{
public static int DivideByAndRoundUp(this int number, int divideBy)
{
return (int)Math.Ceiling((float)number / (float)divideBy);
}
}
Questo rende anche leggibile il tuo codice:
int result = myInt.DivideByAndRoundUp(4);
Potresti scrivere un aiutante.
static int DivideRoundUp(int p1, int p2) {
return (int)Math.Ceiling((double)p1 / p2);
}
Puoi usare qualcosa di simile al seguente.
a / b + ((Math.Sign(a) * Math.Sign(b) > 0) && (a % b != 0)) ? 1 : 0)
Alcune delle risposte sopra usano float, questo è inefficiente e davvero non necessario. Per gli integ non firmati questa è una risposta efficace per int1 / int2:
(int1 == 0) ? 0 : (int1 - 1) / int2 + 1;
Per gli integri firmati questo non sarà corretto
Il problema con tutte le soluzioni qui è che hanno bisogno di un cast o hanno un problema numerico. Lanciare in modalità float o double è sempre un'opzione, ma possiamo fare di meglio.
Quando si utilizza il codice della risposta da @jerryjvl
int div = myInt1 / myInt2;
if ((div >= 0) && (myInt1 % myInt2 != 0))
div++;
c'è un errore di arrotondamento. 1/5 arrotonderebbe per eccesso, perché 1% 5! = 0. Ma questo è sbagliato, perché l'arrotondamento avverrà solo se si sostituisce 1 con un 3, quindi il risultato è 0,6. Dobbiamo trovare un modo per arrotondare quando il calcolo ci fornisce un valore maggiore o uguale a 0,5. Il risultato dell'operatore modulo nell'esempio superiore ha un intervallo compreso tra 0 e myInt2-1. L'arrotondamento avverrà solo se il resto è maggiore del 50% del divisore. Quindi il codice modificato è simile al seguente:
int div = myInt1 / myInt2;
if (myInt1 % myInt2 >= myInt2 / 2)
div++;
Ovviamente abbiamo un problema di arrotondamento anche su myInt2 / 2, ma questo risultato ti darà una soluzione di arrotondamento migliore rispetto agli altri su questo sito.