Pourquoi préférer start + (end - start) / 2 over (start + end) / 2 lors du calcul du milieu d'un tableau?

160

J'ai vu des programmeurs utiliser la formule

mid = start + (end - start) / 2

au lieu d'utiliser la formule plus simple

mid = (start + end) / 2

pour trouver l'élément du milieu dans le tableau ou la liste.

Pourquoi utilisent-ils l'ancien?

Pallavi Chauhan
la source
51
Imagination sauvage: (start + end)pourrait déborder, alors que (end - start)ne le peut pas.
cadaniluk
30
car ce dernier ne fonctionne pas quand startet endsont pointeur.
ensc
20
start + (end - start) / 2aussi porte une signification sémantique: (end - start)est la longueur, donc ce dit: start + half the length.
njzk2
2
@ LưuVĩnhPhúc: Cette question n'a-t-elle pas les meilleures réponses et le plus de votes? Si tel est le cas, les autres questions devraient probablement être fermées comme une dupe de celle-ci. L'âge des messages n'a pas d'importance.
Nisse Engström

Réponses:

218

Il y a trois raisons.

Tout d'abord, start + (end - start) / 2fonctionne même si vous utilisez des pointeurs, à condition qu'il end - startne déborde pas de 1 .

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

Deuxièmement, start + (end - start) / 2ne débordera pas si startet endsont de grands nombres positifs. Avec les opérandes signés, le débordement n'est pas défini:

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

(Notez que cela end - startpeut déborder, mais seulement si start < 0ou end < 0.)

Ou avec l'arithmétique non signée, le débordement est défini mais vous donne la mauvaise réponse. Cependant, pour les opérandes non signés, start + (end - start) / 2ne débordera jamais tant que end >= start.

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

Enfin, vous souhaitez souvent arrondir vers l' startélément.

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

Notes de bas de page

1 Selon la norme C, si le résultat de la soustraction du pointeur n'est pas représentable comme a ptrdiff_t, alors le comportement est indéfini. Cependant, en pratique, cela nécessite d'allouer un chartableau utilisant au moins la moitié de l'espace d'adressage entier.

Dietrich Epp
la source
Le résultat de (end - start)dans le signed intcas n'est pas défini lorsqu'il déborde.
ensc
Pouvez-vous prouver que cela end-startne débordera pas? AFAIK si vous prenez un négatif, startil devrait être possible de le faire déborder. Bien sûr, la plupart du temps, lorsque vous calculez la moyenne, vous savez que les valeurs sont >= 0...
Bakuriu
12
@Bakuriu: Il est impossible de prouver quelque chose qui n'est pas vrai.
Dietrich Epp
4
C'est d'un intérêt particulier en C, car la soustraction du pointeur (selon la norme) est interrompue par conception. Les implémentations sont autorisées à créer des tableaux si grands qu'ils ne end - startsont pas définis, car les tailles des objets ne sont pas signées tandis que les différences de pointeur sont signées. Donc, end - start"fonctionne même en utilisant des pointeurs", à condition que vous gardiez également la taille du tableau ci-dessous PTRDIFF_MAX. Pour être juste avec la norme, ce n'est pas vraiment un obstacle sur la plupart des architectures car c'est la moitié de la taille de la carte mémoire.
Steve Jessop
3
@Bakuriu: Au fait, il y a un bouton "modifier" sur le message que vous pouvez utiliser pour suggérer des changements (ou les faire vous-même) si vous pensez que j'ai manqué quelque chose ou si quelque chose n'est pas clair. Je ne suis qu'un humain, et ce message a été vu par plus de deux mille paires de globes oculaires. Le genre de commentaire, "Vous devriez clarifier ..." me frotte vraiment dans le mauvais sens.
Dietrich Epp
18

Nous pouvons prendre un exemple simple pour démontrer ce fait. Supposons que dans un certain grand tableau, nous essayons de trouver le milieu de la plage [1000, INT_MAX]. Maintenant, INT_MAXest la plus grande valeur que le inttype de données peut stocker. Même si1 on ajoute à cela, la valeur finale deviendra négative.

Aussi, start = 1000et end = INT_MAX.

En utilisant la formule: (start + end)/2,

le point médian sera

(1000 + INT_MAX)/2= -(INT_MAX+999)/2, qui est négatif et peut donner une erreur de segmentation si nous essayons d'indexer en utilisant cette valeur.

Mais, en utilisant la formule (start + (end-start)/2), nous obtenons:

(1000 + (INT_MAX-1000)/2)= (1000 + INT_MAX/2 - 500)= (INT_MAX/2 + 500) qui ne débordera pas .

Shubham
la source
1
Si vous ajoutez 1 à INT_MAX, le résultat ne sera pas négatif, mais indéfini.
celtschk
@celtschk Théoriquement, oui. Pratiquement, cela se terminera la plupart du temps allant de INT_MAXà -INT_MAX. C'est une mauvaise habitude de se fier à cela.
Mât
17

Pour ajouter à ce que d'autres ont déjà dit, le premier explique sa signification plus clairement à ceux qui sont moins mathématiciens:

mid = start + (end - start) / 2

se lit comme suit:

mi est égal au début plus la moitié de la longueur.

tandis que:

mid = (start + end) / 2

se lit comme suit:

milieu est égal à la moitié du début et de la fin

Ce qui ne semble pas aussi clair que le premier, du moins exprimé ainsi.

comme Kos l'a souligné, il peut également lire:

mid est égal à la moyenne du début et de la fin

Ce qui est plus clair mais toujours pas, du moins à mon avis, aussi clair que le premier.

TheLethalCoder
la source
3
Je comprends votre point de vue, mais c'est vraiment exagéré. Si vous voyez «e - s» et pensez «longueur», alors vous voyez presque sûrement «(s + e) ​​/ 2» et pensez «moyen» ou «moyen».
djechlin
2
@djechlin Les programmeurs sont pauvres en mathématiques. Ils sont occupés à faire leur travail. Ils n'ont pas le temps d'assister aux cours de mathématiques.
Little Alien
1

start + (end-start) / 2 peut éviter un éventuel dépassement, par exemple start = 2 ^ 20 et end = 2 ^ 30

club de combat
la source