2013-10-01 15 views
7

Ogni volta che ho bisogno di una media di due numeri per un algoritmo come la ricerca binaria, ho sempre fare qualcosa di simile:Spiegazione della media sicurezza di due numeri

int mid = low + ((high - low)/2); 

Recentemente ho visto un altro modo per farlo in this post, ma non lo capisco Si dice che si può fare questo in Java:

int mid = (low + high) >>> 1; 

o questo in C++:

int mid = ((unsigned int)low + (unsigned int)high)) >> 1; 

La versione C++ rende essenzialmente entrambi gli operandi non firmato, così facendo un risultato spostamento in uno spostamento aritmetico, invece di una firma cambio. Capisco cosa stanno facendo entrambi questi pezzi di codice, ma come risolve il problema dell'overflow? Ho pensato che l'intero problema era che il valore intermedio high + low potrebbe traboccare?

Edit:

Oh, duh. Tutte le risposte non rispondevano esattamente alla mia domanda, ma è stata la risposta di @John Zeringue a farla scattare. Proverò a spiegare qui.

Il problema con (high + low)/2 in Java non è esattamente quello overflow di high + low (è overflow poiché gli interi sono entrambi firmati, ma tutti i bit sono ancora lì e nessuna informazione è persa). Il problema con l'assunzione della media come questa è la divisione. La divisione opera su un valore con segno, quindi il risultato sarà negativo. L'uso dello spostamento invece dividerà per due ma considererà i bit al posto del segno (trattandolo in modo efficace come non firmato).

+1

Java non ha numeri interi senza segno. Considera cosa succederebbe se il tuo 'int' tracimasse? Considera anche cosa fa ">>>". –

+0

Ti suggerisco di provarlo e vedere che differenza fa. Questo è stato un problema noto per decenni, quindi potresti cercarlo ma imparerai di più capendolo da solo. –

risposta

5

Consideriamo quindi i byte anziché gli interi. L'unica differenza è che un byte è un numero intero a 8 bit, mentre un int ha 32 bit. In Java, entrambi sono sempre firmati, il che significa che il bit iniziale indica se sono positivi (0) o negativi (1).

byte low = Byte.valueOf("01111111", 2); // The maximum byte value 
byte high = low; // This copies low. 

byte sum = low + high; // The bit representation of this is 11111110, which, having a 
         // leading 1, is negative. Consider this the worst case 
         // overflow, since low and high can't be any larger. 

byte mid = sum >>> 1; // This correctly gives us 01111111, fixing the overflow. 

Per ints, è la stessa cosa. Fondamentalmente il succo di tutto ciò è che l'utilizzo di un bithift non firmato su interi con segno consente di sfruttare il bit iniziale per gestire i valori più elevati possibili di basso e alto.

+0

Bella spiegazione. Ma se perdonassi la mia impertinenza, e se entrambi i numeri fossero negativi? Dal tuo esempio sembrerebbe che il bit del segno potrebbe/potrebbe essere perso da quel bit shift finale. –

+0

Questo è corretto. Per i numeri negativi, dovresti usare >>, lo spostamento di bit con segno. Si potrebbe facilmente gestire questo caso con un'istruzione if-else. Gli unici numeri su cui questo metodo non funziona sono (per byte) -128 e -128. Per altri tipi interi, è sempre il valore minimo più se stesso. Se sei curioso, ti consiglio di giocarci da solo. Non vorrei usare i byte però. Si scopre che Java non supporta l'aggiunta di byte true, quindi il codice che ho dato sopra non funziona in pratica. –

+0

Si noti che la domanda iniziale riguardava la media degli indici e che i numeri negativi non rappresentano un problema in tale contesto. –

0

Non è possibile utilizzare un int unsigned in Java. In caso di overflow, vengono considerati i 32 bit bassi e i bit di ordine alto vengono scartati. Lo spostamento a destra senza segno ti aiuterà a trattare l'int come unsigned int. Tuttavia, in C++ non avrai l'overflow.

1

La versione C++ non risolve il problema di overflow. Risolve solo il problema di dividere con successo per 2 usando shift invece di /, un'ottimizzazione che il tuo compilatore dovrebbe essere in grado di fare da sola se si trattasse di un miglioramento delle prestazioni.

D'altro canto, l'overflow può non essere un problema reale, se i tipi interi sono sufficientemente grandi da contenere un intervallo ragionevole di indici.

0

siete al sicuro da integer overflow utilizzando il modo in cui lei ha detto che già utilizzate, che è:

int mid = low + ((high - low)/2); 

Let si compilatore farlo proprio lavoro per ottimizzare questo se è necessario.

+0

L'altro codice è molto più leggibile di questo ed entrambi probabilmente avranno la stessa velocità. Le mie domande è il motivo per cui funziona ... – gsingh2011

+0

Che dire di overflow sull'addizione della mano destra? Se alto è INT_MAX e basso è negativo, l'aggiunta sarà overflow => comportamento non definito. – Phil

2

La versione C++ ha un trucco nascosto: low e high sono int s ma non sono mai negativi. Quando li trasmetti a unsigned int, il tuo bit di segno diventa un bit di precisione extra, che una singola aggiunta non può eccedere.

Non è un trucco molto buono perché gli indici di array dovrebbero essere unsigned in ogni caso.

Come è stato detto altrove, i >> 1 significa /2 per gli interi senza segno.

4

Il codice che hai visto è rotto: non calcola correttamente la media dei numeri negativi. Se stai operando solo su valori non negativi, come gli indici, va bene, ma non è una sostituzione generale. Il codice si ha in origine,

int mid = low + ((high - low)/2); 

non è al sicuro da troppo pieno o perché la differenza high - low potrebbe traboccare il campo per gli interi con segno. Di nuovo, se lavori solo con numeri interi non negativi, va bene.

sfruttando il fatto che A+B = 2*(A&B) + A^B possiamo calcolare la media di due numeri interi senza troppo pieno in questo modo:

int mid = (high&low) + (high^low)/2; 

È possibile calcolare la divisione per 2 utilizzando un cambiamento po ', ma tenere a mente i due non la sono lo stesso: la divisione arrotonda verso 0 mentre il bit shift si arrotonda sempre verso il basso.

int mid = (high&low) + ((high^low)>>1); 
+0

Se si desidera che avg (-x, y) sia uguale a -avg (x, y), l'approccio basato sulla divisione è migliore. Se si vuole avg (x + n, y + n) uguale a avg (x, y) + n, un approccio con divisione basata su turni può essere migliore. – supercat

Problemi correlati