Я слышал, что при вычислении среднего значения start + (end-start)/2 отличается от (start + end)/2, потому что последнее может вызвать переполнение. Я не совсем понимаю, почему эта вторая может вызвать переполнение, а первая - нет. Каково общее правило для реализации математической формулы, которая может избежать переполнения.
Проблемы с переполнением при реализации математических формул
Ответ 1
Предположим, вы используете компьютер, где максимальное целочисленное значение равно 10, и вы хотите вычислить среднее значение 5 и 7.
Первый метод (begin + (end-begin)/2) дает
5 + (7-5)/2 == 5 + 2/2 == 6
Второй метод (begin + end)/2 дает переполнение, так как промежуточное значение 12 превышает максимальное значение 10, которое мы принимаем и "обертываем" на что-то другое (если вы используете неподписанные числа, обычные для оберните назад к нулю, но если ваши номера подписаны, вы можете получить отрицательное число!).
12/2 => overflow occurs => 2/2 == 1
Конечно, в реальных компьютерах целые числа переполняются с большим значением, например 2 ^ 32 вместо 10, но идея такая же. К сожалению, нет "общего" способа избавиться от переполнения, о котором я знаю, и это сильно зависит от того, какой конкретный алгоритм вы используете. И тогда события становятся все сложнее. Вы можете получить другое поведение в зависимости от того, какой тип номера вы используете под капотом, и есть другие виды числовых ошибок, о которых можно беспокоиться в дополнение к переполнению и недоиспользованию.
Ответ 2
Оба ваши формулы будут переполняться, но при разных обстоятельствах:
- Часть
(start+end)
вашей формулы(start+end)/2
будет переполняться, когдаstart
иend
будут близки к целочисленному пределу на той же стороне диапазона (т.е. как положительные, так и отрицательные). - Часть
(end-start)
формулыstart+(end-start)/2
будет переполняться, когдаstart
будет положительной, аend
будет отрицательной, и оба значения будут близки к соответствующим концам представляемых целых значений.
Нет "общих" правил, вы делаете это в каждом конкретном случае: посмотрите на части своей формулы, подумайте о ситуациях, которые могут вызвать переполнение, и придумайте, как избежать этого. Например, можно показать формулу start+(end-start)/2
, чтобы избежать переполнения при средних значениях с тем же знаком.
Это трудный путь; простым способом является использование представлений более высокой емкости для промежуточных результатов. Например, если вы используете long long
вместо int
для промежуточных вычислений и скопируйте результаты обратно на int
только когда вы закончите, вы избежите переполнения, считая, что конечный результат соответствует int
.
Ответ 3
При использовании целых чисел вы, вероятно, заботитесь о целочисленном переполнении при принятии таких стратегий.
Обратите внимание, что с использованием формулы b+(b-a)/2
вы должны убедиться, что a <= b
. В противном случае вы можете получить ту же проблему на нижней границе возможного диапазона значений. Подумайте о a/2+b/2
. Однако есть и другие недостатки этого подхода.
При работе с числами с плавающей запятой возникает еще одна проблема, катастрофическая отмена. Из-за ограниченного числа значимых цифр представления с плавающей запятой точность теряется при добавлении больших чисел (даже если это всего лишь промежуточный шаг).
Для решения этой проблемы численной стабильности, например. этот алгоритм может быть использован (слегка адаптирован из wikipedia):
def online_mean(data):
n = 0
mean = 0
for x in data:
n = n + 1
delta = x - mean
mean = mean + delta/n
return mean
Я как-то чувствовал, что существует связь с формулой, которую вы указали выше...
Ответ 4
В двоичном поиске мы напишем следующий код:
if(start > end){
return;
}
int mid = start + (end - start) / 2;
Используя start + (end - start) / 2
, мы можем избежать проблем, на которые указывает @dasblinkenlight
если мы используем (start + end) / 2
, он будет переполняться, как показано dasblinkenlight