web-dev-qa-db-fra.com

Pourquoi préférer début + (fin - début) / 2 sur (début + fin) / 2 lors du calcul du milieu d’un tableau?

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?

159
Pallavi Chauhan

Il y a trois raisons.

Tout d'abord, start + (end - start) / 2 fonctionne même si vous utilisez des pointeurs, tant que end - start Ne déborde pas1.

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) / 2 ne débordera pas si start et end sont 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 end - start Peut déborder, mais uniquement si start < 0 Ou end < 0.)

Ou avec une 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) / 2 ne débordera jamais aussi longtemps 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 voudrez souvent arrondir vers l’élément start.

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 de pointeur n'est pas représentable sous la forme d'un ptrdiff_t, Le comportement est indéfini. Cependant, dans la pratique, cela nécessite d'allouer un tableau char en utilisant au moins la moitié de l'espace d'adressage total.

217
Dietrich Epp

Nous pouvons prendre un exemple simple pour démontrer ce fait. Supposons que dans un certain tableau large, nous essayons de trouver le point médian de la plage [1000, INT_MAX]. Maintenant, INT_MAX Est la plus grande valeur que le type de données int puisse stocker. Même si 1 Est ajouté à cela, la valeur finale deviendra négative.

En outre, start = 1000 Et end = INT_MAX.

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

le milieu sera

(1000 + INT_MAX)/2 = -(INT_MAX+999)/2, qui est négatif et peut donner l'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.

17
Shubham

Pour ajouter à ce que d’autres ont déjà dit, le premier explique plus clairement sa signification à ceux qui ont un esprit mathématique:

mid = start + (end - start) / 2

se lit comme:

milieu égal à départ plus la moitié de la longueur.

tandis que:

mid = (start + end) / 2

se lit comme:

mi égale à la moitié du début plus la fin

Ce qui ne semble pas aussi clair que le premier, du moins lorsqu'il est exprimé comme ça.

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

mi est égal à la moyenne de début et de fin

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

16
TheLethalCoder

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

0
fight_club