Подтвердить что ты не робот

Каков правильный способ найти среднее значение двух значений?

Недавно я узнал, что целочисленное переполнение - это поведение undefined в C (вопрос стороны - это также UB в С++?)

Часто в программировании на С необходимо найти среднее значение двух значений a и b. Однако выполнение (a+b)/2 может привести к переполнению и undefined.

Итак, мой вопрос: какой правильный способ найти среднее значение двух значений a и b в C?

4b9b3361

Ответ 1

С помощью Защищенного кодирования

if (((si_b > 0) && (si_a > (INT_MAX - si_b))) ||
    ((si_b < 0) && (si_a < (INT_MIN - si_b))))
{
  /* will overflow, so use difference method */
  return si_b + (si_a - si_b) / 2;
} 
else
{
 /* the addition will not overflow */
  return (si_a + si_b) / 2;
}

ДОПОЛНЕНИЕ

Благодаря @chux для указания проблемы округления. Здесь версия, проверенная на правильное округление...

int avgnoov (int si_a, int si_b)
{
    if ((si_b > 0) && (si_a > (INT_MAX - si_b)))
    {
      /* will overflow, so use difference method */
      /* both si_a and si_b > 0; 
          we want difference also > 0
          so rounding works correctly */
      if (si_a >= si_b)
        return si_b + (si_a - si_b) / 2;
      else
        return si_a + (si_b - si_a) / 2;
    } 
    else if ((si_b < 0) && (si_a < (INT_MIN - si_b)))
    {
      /* will overflow, so use difference method */
      /* both si_a and si_b < 0; 
          we want difference also < 0
          so rounding works correctly */
      if (si_a <= si_b)
        return si_b + (si_a - si_b) / 2;
      else
        return si_a + (si_b - si_a) / 2;
    }
    else
    {
     /* the addition will not overflow */
      return (si_a + si_b) / 2;
    }
}

Ответ 2

(a >> 1) + (b >> 1) + (((a & 1) + (b & 1)) >> 1)

Оператор сдвига (x → i) в c int математике эквивалентен делению на 2 на степень i. Таким образом, утверждение (a → 1) + (b → 1) совпадает с выражением /2 + b/2. Однако необходимо также добавить среднее значение укороченных частей номера. Это значение может быть получено путем маскировки (a и 1), добавления ((a и 1) + (b и 1)) и деления (((a и 1) + (b и 1)) → 1). Среднее значение становится (a → 1) + (b → 1) + (((a и 1) + (b и 1)) → 1)

Примечание: причина использования → и, а не/и% в качестве операторов деления и останова, является эффективной.

Ответ 3

Простым подходом является следующий

int c = a / 2 + ( b + a % 2 ) / 2;

Например, a и b могут быть представлены как

a = 2 * n + r1;
b = 2 * m + r2;

Тогда

( a + b ) / 2 => ( 2 * n + r1 + 2 * m + r2 ) / 2 => 2 * n / 2 + ( b + r1 ) / 2

И последнее выражение дает вам

=> a / 2 + ( b + a % 2 ) / 2

Более правильным выражением является следующее

int c = a / 2 + b / 2 + ( a % 2 + b % 2 ) / 2;

Например, если мы имеем

int a = INT_MAX;
int b = INT_MAX;

тогда c рассчитывается как

int c = a / 2 + b / 2 + ( a % 2 + b % 2 ) / 2;

даст c == INT_MAX

EDIT: было обнаружено интересное различие между эффектом компьютерных операторов и эффектом математических операторов. Например, согласно математике -1 можно представить как

-1 = -1 * 2 + 1 

что соответствует формуле

a = 2 * n + r1

2 * n должно быть целым числом, меньшим или равным tp a

Таким образом, число меньше -1 равно -2. :)

Я думаю, что общая формула, показанная мной, будет работать, требуется, чтобы для нечетных отрицательных чисел были рассмотрены даже отрицательные числа, которые меньше нечетного отрицательного числа.

кажется, что правильная формула выглядит как

int c = ( a < 0 ? a & ~1 : a ) / 2 + 
        ( b < 0 ? b & ~1 : b ) / 2 + 
        ( ( a & 1 ) + ( b & 1 ) ) / 2;

Важно отметить, что с математической точки зрения среднее значение -1 и -2 должно быть равно -2, и формула дает правильный результат.:)

Ответ 4

Если вы беспокоитесь о переполнении, вы можете применить значения к большему типу для выполнения математики, а затем выполнить проверку границ.

Ответ 5

Это от Вычисление среднего числа двух целых чисел, округленных до нуля в течение одного цикла команды:

(a >> 1) + (b >> 1) + (a & b & 0x1)

Вы должны учитывать, что:

  • В этой реализации определено, правильно ли смещение отрицательного целого смещает нули или единицы в биты верхнего порядка. Многие процессоры часто имеют две разные команды: арифметический сдвиг вправо (сохраняет знаковый бит) и логический сдвиг вправо (не сохраняет знаковый бит). Компилятору разрешено выбирать либо (большинство компиляторов выбирают инструкцию арифметического сдвига).

    ISO/IEC 9899: 2011 §6.5.7 Операторы побитового сдвига

    ¶5 Результат E1 → E2 - это правые сдвиговые позиции E2 в E1. [CUT] Если E1 имеет подписанный тип и отрицательное значение, результирующее значение определяется реализацией.

    Изменение выражения:

    a / 2 + b / 2 + (a & b & 0x1)
    

    не является решением, так как логические сдвиги справа эквивалентны делению на степень 2 только для положительных или беззнаковых чисел.

  • также (a & b & 0x1) не определен. Этот термин должен быть отличным от нуля, если оба a и b являются нечетными. Но это не удается с представлением одного дополнения и ISO C, раздел 6.2.6.2/2, гласит, что реализация может выбрать одно из трех различных представлений для целых типов данных:

    • два дополнения
    • одно дополнение
    • знак/величина

    (обычно два дополнения намного перевешивают остальные).

Ответ 6

Самый простой (и обычно самый быстрый) способ усреднить два int во всем диапазоне [INT_MIN...INT_MAX] - это использовать более широкий целочисленный тип. (Предлагается @user3100381.) Назовем это int2x.

int average_int(int a, int b) {
  return ((int2x) a + b)/2;
}

Конечно, это означает, что существует более широкий тип, поэтому давайте посмотрим на решение, которое не требует более широкого типа.

Задачи:

Q: Когда один int является нечетным, а другой четным, каким образом происходит округление? A: Следуйте average_int() выше и округленно в направлении 0. (truncate).

В: Может ли код использовать %?
A: С кодом pre-C99 результат a % 2 допускает разные результаты при a < 0. Поэтому не будем использовать %.

Q: Должен ли int иметь симметричный диапазон положительных и отрицательных чисел?
A: С C99 число отрицательных чисел совпадает (или еще 1), чем число положительных чисел. Попробуем не требовать этого.

РЕШЕНИЕ:

Выполните тесты, чтобы определить, может ли произойти переполнение. Если нет, просто используйте (a + b) / 2. В противном случае добавьте половину разницы (подписанную так же, как и ответ) на меньшее значение.

Ниже приводится тот же ответ, что и average_int(), не прибегая к более широкому целочисленному типу. Он защищает от переполнения int и не требует, чтобы INT_MIN + INT_MAX был 0 или -1. Это не зависит от кодирования как 2 дополнения, 1 дополнения или знака-величины.

int avgC2(int a, int b) {
  if (a >= 0) {
    if (b > (INT_MAX - a)) {
      // (a+b) > INT_MAX
      if (a >= b) {
        return (a - b) / 2 + b;
      } else {
        return (b - a) / 2 + a;
      }
    }
  } else {
    if (b < (INT_MIN - a)) {
      // (a+b) < INT_MIN
      if (a <= b) {
        return (a - b) / 2 + b;
      } else {
        return (b - a) / 2 + a;
      }
    }
  }
  return (a + b) / 2;
}

Не более 3 if() встречаются с любой парой int.

Ответ 7

Самый простой ответ, если есть только 2 элемента, чтобы избежать переполнения, было бы:

(a/2) + (b/2) = average

Для большего количества элементов вы можете использовать:

(a/x) + (b/x) + (c/x) + (d/x) ..... = average //x = amount of elements

В математической точке зрения это никогда не достигнет переполнения, если ранее ни одно из исходных значений не выполнялось ранее, так как вы действительно не добавляете их все вместе, а скорее разделение их перед добавлением их вместе. Таким образом, нет результата любой операции, выполняемой во время вычисления, включая результат, будет когда-либо больше (по обе стороны от 0) , чем самый большой начальный элемент strong > (предполагая, что вы работаете только с Real Numbers).

Выполните следующие действия:

  • Определите количество элементов в 'C', позвоните на него total.
  • Объявить значение для хранения среднего значения, назовите его average.
  • Объявить значение для хранения остатков, называть его remainder.
  • Итерации через них и:
    • Разделите текущий элемент на общую сумму, total.
    • Добавьте результат к среднему значению average.
    • Добавьте оставшуюся часть разделенных значений вместе, remainder.
  • Разделите остатки и добавьте их в среднее значение, average.
  • Сделайте со средним, что вам нужно/намеревается.

Это даст вам ответ максимум на максимум 1 (десятичная цифровая система [база 10]). Я еще не знаю С++, поэтому я могу привести только пример на С#.

Псевдокод в С# (только для того, чтобы представить идею):

int[] C = new int[20];            //The array of elements.
int total = C.Length;             //The total amount of elements.
int average = 0;                  //The variable to contain the result.
int remainder = 0;                //The variable to contain all the smaller bits.
foreach (int element in C)        //Iteration
{
    int temp = (element / total); //Divide the current element by the total.
    average = average + temp;     //Add the result to the average.
    temp = (temp % total);        //Get the remainder (number that was not divided)
    remainder = remainder + temp; //Add remainder to the other remainders.
}
average = average + (remainder / total); // Adds the divided remainders to the average.

Сжатый пример С#:

int[] C = new int[20];               //The array of elements.
int total = C.Length;                //The total amount of elements.
int average = 0;                     //The variable to contain the result.
int remainder = 0;                   //The variable to contain all the smaller bits.
foreach (int element in C)           //Iteration
{
    average += (element / total);    //Add the current element divided by the total to the average.
    remainder += ( element % total); //Add the remainders together.
}
average += (remainder / total); //Adds the divided remainders to the total.