Возьмите среднее из двух чисел со знаком в C - PullRequest
10 голосов
/ 18 апреля 2011

Допустим, у нас есть x и y, и оба являются целыми числами со знаком в C, как мы можем найти наиболее точное среднее значение между двумя?

Я бы предпочел решение, которое не использует какие-либо преимуществаспецифичные для машины / компилятора / цепочки инструментов.

Лучшее, что я придумал, это: (a / 2) + (b / 2) + !!(a % 2) * !!(b %2) Есть ли более точное решение?Быстрее?Проще?

Что если мы узнаем, что один априори больше другого?

Спасибо.

D


РедакторПримечание : Обратите внимание, что OP ожидает ответы, которые не подлежат целочисленному переполнению, когда входные значения близки к максимальным абсолютным пределам типа C int.Это не было указано в исходном вопросе, но важно при предоставлении ответа.

Ответы [ 7 ]

7 голосов
/ 23 апреля 2015

После принятия ответа (4 года)

Я бы ожидал, что функция int average_int(int a, int b) будет:
1. Работайте по всему диапазону [INT_MIN..INT_MAX] для комбинаций a и b.
2. Получите тот же результат, что и (a+b)/2, как при использовании более широкой математики.

Когда существует int2x , подход @ Сантьяго Алессандри работает хорошо.

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

В противном случае вариант @ AProgrammer :

int avgC(int a, int b) {
  if ((a < 0) == (b < 0)) {  // a,b same sign
    return a/2 + b/2 + (a%2 + b%2)/2;
  }
  return (a+b)/2;
}

A решение с большим количеством тестов, но без %

Все приведенные ниже решения «работали» с точностью до 1 из (a+b)/2, когда переполнения не произошло, но я надеялся найти решение, которое соответствует (a+b)/2 для всех int.


@ Сантьяго Алессандри Решение работает до тех пор, пока диапазон int уже, чем диапазон long long - который обычно равен * .

((long long)a + (long long)b) / 2

@ AProgrammer , принятый ответ, не соответствует примерно 1/4 времени совпадения (a+b)/2. Пример ввода, например a == 1, b == -2

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

@ Guy Sirton , Решение не удается примерно в 1/8 времени, чтобы соответствовать (a+b)/2. Пример ввода, например a == 1, b == 0

int sgeq = ((a<0)==(b<0));
int avg = ((!sgeq)*(a+b)+sgeq*(b-a))/2 + sgeq*a;

@ R .. , Решение не удается примерно в 1/4 времени от совпадения (a+b)/2. Пример ввода, например a == 1, b == 1

return (a-(a|b)+b)/2+(a|b)/2;

@ MatthewD , теперь удаленному решению не удается примерно 5/6 времени совпадения с (a+b)/2. Пример ввода, например a == 1, b == -2

unsigned diff;
signed mean;
if (a > b) {
    diff = a - b;
    mean = b + (diff >> 1);
} else {
    diff = b - a;
    mean = a + (diff >> 1);
}
3 голосов
/ 18 апреля 2011
a/2 + b/2 + (a%2 + b%2)/2

Кажется самым простым из них, который не учитывает никаких предположений о характеристиках реализации (он зависит от C99, который указывает результат / как "усеченный до 0", в то время как он зависел от реализации для C90).

Преимущество состоит в том, что у него нет теста (и, следовательно, нет дорогостоящих переходов), а все деления / остатки делятся на 2, так что компилятор может использовать методы битового перемешивания.

3 голосов
/ 18 апреля 2011

Если (a^b)<=0, вы можете просто использовать (a+b)/2, не опасаясь переполнения.

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

Я сделал это быстро с макушки головы, чтобы могБудут какие-то глупые ошибки.Обратите внимание, что здесь нет машинно-зависимых хаков . Все поведение полностью определяется стандартом C и тем фактом, что оно требует представления знаковых значений с двойным дополнением, единичным дополнением или величиной знака и указывает, что побитовые операторы работают с побитовым представлением. Нет, относительная величина a|b зависит от представления ...

Редактировать: Вы также можете использовать a+(b-a)/2, когда они имеют одинаковый знак.Обратите внимание, что это даст уклон в сторону a.Вы можете повернуть его вспять и получить уклон в сторону b.Мое решение, приведенное выше, с другой стороны, дает уклон в сторону нуля, если я не ошибаюсь.

Еще одна попытка: Один стандартный подход - (a&b)+(a^b)/2.В дополнение к двум это работает независимо от знаков, но я полагаю, что это также работает в дополнение или знаковые величины, если a и b имеют один и тот же знак.Хотите проверить это?

1 голос
/ 16 марта 2016

Для целых чисел без знака среднее значение равно полу (x + y) / 2. Но то же самое не работает для целых чисел со знаком. Эта формула не работает для целых чисел, сумма которых является нечетным числом, поскольку их пол на единицу меньше их среднего значения.

Вы можете прочитать больше на Восторг Хакера в разделе 2.5

Код для вычисления среднего значения 2 целых чисел без знака переполнения:

int t = (a & b) + ((a ^ b) >> 1)
unsigned t_u = (unsigned)t
int avg = t + ( (t_u >> 31 ) & (a ^ b) )

Я проверил его правильность, используя Z3 SMT solver

1 голос
/ 18 апреля 2011

Несколько замечаний, которые могут помочь:

«Самый точный» не обязательно уникален для целых чисел.Например, для 1 и 4, 2 и 3 одинаково «самый точный» ответ.Математически (не целые числа C):

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

Давайте попробуем разобрать это:

  • Если знак (a)! = Знак (b), то a + b не будет переполнен,Этот случай может быть определен путем сравнения старшего значащего бита в представлении дополнения до двух.
  • Если знак (a) == знак (b), то, если a больше b, (ab) не будет переполнен.В противном случае (ba) не переполнится.РЕДАКТИРОВАТЬ: На самом деле ни один не будет переполнен.

Что вы пытаетесь оптимизировать точно?Разные архитектуры процессоров могут иметь разные оптимальные решения.Например, в вашем коде замена умножения на AND может повысить производительность.Также в архитектуре дополнения до двух вы можете просто (a & b & 1).

Я просто собираюсь выбросить некоторый код, не выглядя слишком быстро, но, возможно, кто-то может использовать и улучшать:

int sgeq = ((a<0)==(b<0));
int avg = ((!sgeq)*(a+b)+sgeq*(b-a))/2 + sgeq*a
0 голосов
/ 23 декабря 2016

Этот ответ подходит для любого числа целых чисел:

    int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    decimal avg = 0;
    for (int i = 0; i < array.Length; i++){
        avg = (array[i] - avg) / (i+1) + avg;
    }

ожидает avg == 5,0 для этого теста

0 голосов
/ 18 апреля 2011

Я бы сделал это, конвертировал оба в длинные длинные (64-битные целые числа со знаком), сложил их, это не переполнит, а затем разделил бы результат на 2:

((long long)a + (long long)b) / 2

Если вам нужна десятичная часть, сохраните ее как двойную

Важно отметить, что результат будет помещаться в 32-разрядное целое число.

Если вы используете целое число высшего ранга, вы можете использовать:

((double)a + (double)b) / 2
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...