Perché preferire start + (end - start) / 2 su (start + end) / 2 quando si calcola il centro di un array?


160

Ho visto i programmatori usare la formula

mid = start + (end - start) / 2

invece di usare la formula più semplice

mid = (start + end) / 2

per trovare l'elemento centrale nella matrice o nell'elenco.

Perché usano il primo?


51
Immaginazione selvaggia: (start + end)potrebbe traboccare, mentre (end - start)non può.
Cadaniluk,

30
perché quest'ultimo non funziona quando starte endsono puntatore.
ensc


20
start + (end - start) / 2svolge anche significato semantico: (end - start)è la lunghezza, quindi questo dice: start + half the length.
njzk2,

2
@ LưuVĩnhPhúc: questa domanda non ha le risposte migliori e il maggior numero di voti? In tal caso, le altre domande dovrebbero probabilmente essere chiuse come duplicazione di questa. L'età dei post è irrilevante.
Nisse Engström,

Risposte:


218

Ci sono tre ragioni.

Prima di tutto, start + (end - start) / 2funziona anche se si utilizzano i puntatori, purché end - startnon trabocchi 1 .

int *start = ..., *end = ...;
int *mid = start + (end - start) / 2; // works as expected
int *mid = (start + end) / 2;         // type error, won't compile

In secondo luogo, start + (end - start) / 2non traboccerà se starte endsono grandi numeri positivi. Con gli operandi firmati, l'overflow non è definito:

int start = 0x7ffffffe, end = 0x7fffffff;
int mid = start + (end - start) / 2; // works as expected
int mid = (start + end) / 2;         // overflow... undefined

(Nota che end - startpotrebbe traboccare, ma solo se start < 0o end < 0.)

O con aritmetica senza segno, l'overflow è definito ma ti dà la risposta sbagliata. Tuttavia, per gli operandi non firmati, start + (end - start) / 2non sarà mai in overflow fino a quando end >= start.

unsigned start = 0xfffffffeu, end = 0xffffffffu;
unsigned mid = start + (end - start) / 2; // works as expected
unsigned mid = (start + end) / 2;         // mid = 0x7ffffffe

Infine, spesso vuoi arrotondare verso l' startelemento.

int start = -3, end = 0;
int mid = start + (end - start) / 2; // -2, closer to start
int mid = (start + end) / 2;         // -1, surprise!

Le note

1 Secondo lo standard C, se il risultato della sottrazione del puntatore non è rappresentabile come a ptrdiff_t, il comportamento non è definito. Tuttavia, in pratica, ciò richiede l'allocazione di un chararray utilizzando almeno metà dell'intero spazio degli indirizzi.


il risultato di (end - start)nel signed intcaso non è definito quando trabocca.
ensc

Puoi provare che end-startnon traboccerà? AFAIK se prendi un negativo startdovrebbe essere possibile farlo traboccare. Certo, il più delle volte quando si calcola la media si sa che i valori sono >= 0...
Bakuriu

12
@Bakuriu: è impossibile provare qualcosa che non è vero.
Dietrich Epp,

4
È di particolare interesse in C, poiché la sottrazione del puntatore (secondo lo standard) è interrotta dal design. Le implementazioni sono autorizzate a creare array così grandi da end - startnon essere definiti, poiché le dimensioni degli oggetti non sono firmate mentre le differenze del puntatore sono firmate. Quindi end - start"funziona anche usando i puntatori", purché tu mantenga in qualche modo anche la dimensione dell'array sottostante PTRDIFF_MAX. Per essere onesti con lo standard, non è un grosso ostacolo per la maggior parte delle architetture poiché è la metà della dimensione della mappa di memoria.
Steve Jessop,

3
@Bakuriu: A proposito, c'è un pulsante "modifica" sul post che puoi usare per suggerire modifiche (o apportarle tu stesso) se pensi di aver perso qualcosa o qualcosa di poco chiaro. Sono solo umano, e questo post è stato visto da oltre duemila paia di bulbi oculari. Il tipo di commento "Dovresti chiarire ..." mi strofina davvero nel modo sbagliato.
Dietrich Epp,

18

Possiamo fare un semplice esempio per dimostrare questo fatto. Supponiamo che in un certo array di grandi dimensioni , stiamo cercando di trovare il punto medio dell'intervallo [1000, INT_MAX]. Ora, INT_MAXè il valore più grande che il inttipo di dati può memorizzare. Anche se 1aggiunto a questo, il valore finale diventerà negativo.

Inoltre, start = 1000e end = INT_MAX.

Utilizzando la formula: (start + end)/2,

il punto medio sarà

(1000 + INT_MAX)/2= -(INT_MAX+999)/2, che è negativo e può dare un errore di segmentazione se proviamo a indicizzare usando questo valore.

Ma, usando la formula, (start + (end-start)/2)otteniamo:

(1000 + (INT_MAX-1000)/2)= (1000 + INT_MAX/2 - 500)= (INT_MAX/2 + 500) che non trabocca .


1
Se aggiungi 1 a INT_MAX, il risultato non sarà negativo, ma indefinito.
Celtschk,

@celtschk Teoricamente, sì. Praticamente avvolgerà un sacco di volte passando da INT_MAXa -INT_MAX. È una cattiva abitudine fare affidamento su questo però.
Albero

17

Per aggiungere ciò che altri hanno già detto, il primo spiega il suo significato in modo più chiaro a quelli meno matematici:

mid = start + (end - start) / 2

si legge come:

metà equivale all'inizio più metà della lunghezza.

mentre:

mid = (start + end) / 2

si legge come:

metà è uguale alla metà di inizio più fine

Che non sembra chiaro come il primo, almeno quando espresso in questo modo.

come ha sottolineato Kos, può anche leggere:

metà è uguale alla media di inizio e fine

Il che è più chiaro ma non ancora, almeno secondo me, chiaro come il primo.


3
Vedo il tuo punto, ma questo è davvero un tratto. Se vedi "e - s" e pensi a "lunghezza", allora quasi sicuramente vedi "(s + e) ​​/ 2" e pensi a "medio" o "medio".
Djechlin,

2
@djechlin I programmatori sono scarsi in matematica. Sono impegnati a fare il loro lavoro. Non hanno tempo di frequentare le lezioni di matematica.
Little Alien,

1

start + (end-start) / 2 può evitare possibili overflow, ad esempio start = 2 ^ 20 e end = 2 ^ 30

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.