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.