Я видел, как программисты использовали формулу
mid = start + (end - start) / 2
вместо простой формулы
mid = (start + end) / 2
для нахождения среднего элемента в массиве или списке.
Почему они используют первый?
Я видел, как программисты использовали формулу
mid = start + (end - start) / 2
вместо простой формулы
mid = (start + end) / 2
для нахождения среднего элемента в массиве или списке.
Почему они используют первый?
Есть три причины.
Прежде всего, start + (end - start) / 2
работает, даже если вы используете указатели, пока end - start
не переполняет 1.
int *start = ..., *end = ...;
int *mid = start + (end - start) / 2; // works as expected
int *mid = (start + end) / 2; // type error, won't compile
Во-вторых, start + (end - start) / 2
не будет переполняться, если start
и end
- большие положительные числа. С подписанными операндами переполнение undefined:
int start = 0x7ffffffe, end = 0x7fffffff;
int mid = start + (end - start) / 2; // works as expected
int mid = (start + end) / 2; // overflow... undefined
(Обратите внимание, что end - start
может переполняться, но только если start < 0
или end < 0
.)
Или с беззнаковой арифметикой, переполнение определено, но дает неверный ответ. Однако для неподписанных операндов start + (end - start) / 2
никогда не будет переполняться до тех пор, пока end >= start
.
unsigned start = 0xfffffffeu, end = 0xffffffffu;
unsigned mid = start + (end - start) / 2; // works as expected
unsigned mid = (start + end) / 2; // mid = 0x7ffffffe
Наконец, вы часто хотите округлить к элементу start
.
int start = -3, end = 0;
int mid = start + (end - start) / 2; // -2, closer to start
int mid = (start + end) / 2; // -1, surprise!
1 Согласно стандарту C, если результат вычитания указателя не представляется в виде ptrdiff_t
, то поведение undefined. Однако на практике это требует выделения массива char
, использующего по меньшей мере половину всего адресного пространства.
Мы можем сделать простой пример, чтобы продемонстрировать этот факт. Предположим, что в некотором большом массиве мы пытаемся найти среднюю точку диапазона [1000, INT_MAX]
. Теперь INT_MAX
- наибольшее значение, которое может хранить тип данных int
. Даже если к этому добавляется 1
, окончательное значение станет отрицательным.
Кроме того, start = 1000
и end = INT_MAX
.
Используя формулу: (start + end)/2
,
средняя точка будет
(1000 + INT_MAX)/2
=-(INT_MAX+999)/2
, который отрицательный и может дать ошибку сегментации, если мы попытаемся индексировать это значение.
Но, используя формулу (start + (end-start)/2)
, получим:
(1000 + (INT_MAX-1000)/2)
=(1000 + INT_MAX/2 - 500)
=(INT_MAX/2 + 500)
, который не будет переполняться.
Чтобы добавить к тому, что уже сказали другие, первый объясняет его смысл понятнее тем, кто менее математически настроен:
mid = start + (end - start) / 2
читается как:
середина равна началу плюс половина длины.
тогда:
mid = (start + end) / 2
читается как:
середина равна половине начала плюс конец
Что не кажется таким ясным, как первое, по крайней мере, когда оно выражается так.
как указал Кос, он также может читать:
середина равна среднему значению начала и конца
Что яснее, но по-прежнему нет, по крайней мере, по-моему, так же ясно, как и первое.
start + (end-start)/2 позволяет избежать возможного переполнения, например start = 2 ^ 20 и end = 2 ^ 30