Warum Start + (Ende - Start) / 2 gegenüber (Start + Ende) / 2 bevorzugen, wenn die Mitte eines Arrays berechnet wird?


160

Ich habe gesehen, wie Programmierer die Formel verwendet haben

mid = start + (end - start) / 2

anstatt die einfachere Formel zu verwenden

mid = (start + end) / 2

zum Finden des mittleren Elements im Array oder in der Liste.

Warum benutzen sie die erstere?


51
Wilde Vermutung: (start + end)könnte überlaufen, (end - start)kann aber nicht.
Cadaniluk

30
weil letzteres nicht funktioniert wann startund endZeiger sind.
Ensc


20
start + (end - start) / 2trägt auch semantische Bedeutung: (end - start)ist die Länge, so sagt dies : start + half the length.
NJZK2

2
@ LưuVĩnhPhúc: Hat diese Frage nicht die besten Antworten und die meisten Stimmen? Wenn ja, sollten die anderen Fragen wahrscheinlich als Dup dieser Frage geschlossen werden. Das Alter der Beiträge spielt keine Rolle.
Nisse Engström

Antworten:


218

Es gibt drei Gründe.

Funktioniert zunächst start + (end - start) / 2einmal auch, wenn Sie Zeiger verwenden, solange end - startnicht 1 überläuft .

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

Zweitens start + (end - start) / 2wird nicht überlaufen, wenn startund endsind große positive Zahlen. Bei signierten Operanden ist der Überlauf undefiniert:

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

(Beachten Sie, dass dies end - startüberlaufen kann, aber nur, wenn start < 0oder end < 0.)

Oder mit vorzeichenloser Arithmetik wird ein Überlauf definiert, der Ihnen jedoch die falsche Antwort gibt. Bei vorzeichenlosen Operanden start + (end - start) / 2wird jedoch niemals überlaufen, solange end >= start.

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

Schließlich möchten Sie häufig auf das startElement runden .

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

Fußnoten

1 Nach dem C-Standard ist ptrdiff_tdas Verhalten undefiniert , wenn das Ergebnis der Zeigersubtraktion nicht als a dargestellt werden kann. In der Praxis erfordert dies jedoch die Zuweisung eines charArrays unter Verwendung mindestens der Hälfte des gesamten Adressraums.


Ergebnis von (end - start)in dem signed intFall ist undefiniert, wenn es überläuft.
Ensc

Können Sie beweisen, dass es end-startnicht überläuft? AFAIK Wenn Sie ein Negativ nehmen start, sollte es möglich sein, es überlaufen zu lassen. Sicher, die meisten Male, wenn Sie den Durchschnitt berechnen, wissen Sie, dass Werte sind >= 0...
Bakuriu

12
@ Bakuriu: Es ist unmöglich, etwas zu beweisen, was nicht wahr ist.
Dietrich Epp

4
Es ist von besonderem Interesse für C, da die Zeigersubtraktion (gemäß Standard) durch das Design unterbrochen wird. Implementierungen dürfen Arrays erstellen, die so groß sind, dass sie nicht end - startdefiniert sind, da Objektgrößen nicht signiert sind, während Zeigerunterschiede signiert sind. Also end - start"funktioniert sogar mit Zeigern", vorausgesetzt, Sie behalten auch irgendwie die Größe des Arrays unten bei PTRDIFF_MAX. Um dem Standard gerecht zu werden, ist dies für die meisten Architekturen kein großes Hindernis, da dies halb so groß ist wie die Speicherkarte.
Steve Jessop

3
@ Bakuriu: Übrigens gibt es in dem Beitrag eine Schaltfläche "Bearbeiten", mit der Sie Änderungen vorschlagen (oder selbst vornehmen) können, wenn Sie der Meinung sind, dass ich etwas verpasst habe oder etwas unklar ist. Ich bin nur ein Mensch, und dieser Beitrag wurde von über zweitausend Augapfelpaaren gesehen. Die Art von Kommentar "Du solltest klarstellen ..." reibt mich wirklich in die falsche Richtung.
Dietrich Epp

18

Wir können ein einfaches Beispiel nehmen, um diese Tatsache zu demonstrieren. Angenommen, in einem bestimmten großen Array versuchen wir, den Mittelpunkt des Bereichs zu finden [1000, INT_MAX]. Jetzt INT_MAXist der größte Wert, den der intDatentyp speichern kann. Selbst wenn dies 1hinzugefügt wird, wird der Endwert negativ.

Auch start = 1000und end = INT_MAX.

Mit Hilfe der Formel: (start + end)/2,

der Mittelpunkt wird sein

(1000 + INT_MAX)/2= -(INT_MAX+999)/2, was negativ ist und einen Segmentierungsfehler verursachen kann, wenn wir versuchen, mit diesem Wert zu indizieren.

Aber mit der Formel erhalten (start + (end-start)/2)wir:

(1000 + (INT_MAX-1000)/2)= (1000 + INT_MAX/2 - 500)= (INT_MAX/2 + 500) was nicht überläuft .


1
Wenn Sie 1 hinzufügen, INT_MAXist das Ergebnis nicht negativ, sondern undefiniert.
Celtschk

@ Celtschk Theoretisch ja. Praktisch wird es die meiste Zeit von INT_MAXbis umlaufen -INT_MAX. Es ist jedoch eine schlechte Angewohnheit, sich darauf zu verlassen.
Mast

17

Um das zu ergänzen, was andere bereits gesagt haben, erklärt der erste seinen weniger mathematisch denkenden Menschen seine Bedeutung klarer:

mid = start + (end - start) / 2

liest als:

Mitte gleich Start plus die Hälfte der Länge.

wohingegen:

mid = (start + end) / 2

liest als:

Mitte entspricht der Hälfte von Start plus Ende

Was nicht so klar zu sein scheint wie das erste, zumindest wenn man es so ausdrückt.

wie Kos betonte, kann es auch lesen:

mid entspricht dem Durchschnitt von Start und Ende

Was klarer ist, aber meiner Meinung nach immer noch nicht so klar wie das erste.


3
Ich verstehe Ihren Standpunkt, aber das ist wirklich eine Strecke. Wenn Sie "e - s" sehen und "Länge" denken, dann sehen Sie mit ziemlicher Sicherheit "(s + e) ​​/ 2" und denken "Durchschnitt" oder "Mitte".
Djechlin

2
@ djechlin Programmierer sind schlecht in Mathe. Sie sind mit ihrer Arbeit beschäftigt. Sie haben keine Zeit, an den Matheklassen teilzunehmen.
Little Alien

1

start + (end-start) / 2 kann einen möglichen Überlauf vermeiden, zum Beispiel start = 2 ^ 20 und end = 2 ^ 30

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.