Я видел, как программисты использовали формулу
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