Подпись деление с беззнаковым числителем - PullRequest
7 голосов
/ 28 мая 2011

Я пытаюсь вычислить скользящее среднее, и, чтобы попытаться получить и оптимизировать немного, я упростил вычисление, так что есть только одно деление.Когда значение уменьшается, возникает момент, когда текущее значение снижается до уровня ниже среднего.На данный момент средний скачок.Я предполагаю, что это потому, что деление не подписано, а бит знака моего числителя интерпретируется как массивное число без знака.Я просто не уверен, куда мне нужно бросить unsigned, чтобы эта проблема не появлялась.

unsigned int AverageUsage;
unsigned int TotalUsage;
unsigned int incCount;

    AverageUsage = (TotalUsage - AverageUsage)/++incCount + AverageUsage;

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

    AverageUsage = (signed int)(TotalUsage - AverageUsage)/++incCount + AverageUsage;

Устанавливаем числитель со знаком, но я не уверен, как произойдет деление.

    AverageUsage =  (signed int)((signed int)(TotalUsage - AverageUsage)/++incCount) + AverageUsage;

Должно работать (я могу гарантировать результат этого полногооперация никогда не будет отрицательной), но меня беспокоят случаи, когда incCount достигает значения, которое «выглядит» отрицательно.

Существует ли простое решение для этого, которое, мы надеемся,

  • не требуется оператор if
  • Не требует QWORDs

Спасибо!

Ответы [ 5 ]

5 голосов
/ 28 мая 2011

Общее правило двоичных операций C (включая деление) состоит в том, что оба операнда будут преобразованы в один и тот же тип, который является одним из: int, unsigned int, long, unsigned long, intmax_t, uintmax_t, float, double, long double.Если оба операнда имеют типы в этом списке, они оба будут преобразованы в более поздний.Если ни то, ни другое, они оба будут конвертированы в int

Так что в вашем примере:

AverageUsage = (signed int)(TotalUsage - AverageUsage)/++incCount + AverageUsage

, если incCount равно unsigned int, то ваш каст не имеет эффекта -- вычитание будет преобразовано в подписанное int, а затем обратно в unisgned int и будет выполнено беззнаковое делениеЕсли вы хотите подписанное деление, вам понадобится:

AverageUsage = (int)(TotalUsage - AverageUsage)/(int)++incCount + AverageUsage

, что, как вы заметили, может привести к проблемам, если incCount превысит INT_MAX.

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

4 голосов
/ 28 мая 2011

У вас есть 2 варианта.

Использование математики с плавающей точкой

Я думаю, что вы все равно хотите сделать это, чтобы получить правильное среднее значение.

Не существует такого понятия, как смешанное деление с плавающей запятой / на целое. Таким образом, и числитель, и знаменатель будут преобразованы в число с плавающей запятой.

Независимо от того, числитель или знаменатель подписан или не подписан, не имеет значения. Не существует такой вещи как беззнаковая плавающая точка. Знаменатель incCount будет преобразован в число с плавающей запятой, и будет выполнено полное деление с плавающей запятой.

Используйте целочисленное деление и обрабатывайте особые случаи

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

Числитель / Знаменатель подписаны

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

Числитель / Знаменатель не имеют знака

Вы должны сделать числитель без знака и использовать оператор if () для обработки двух случаев: TotalUsage < AverageUsage и TotalUsage > AverageUsage. Здесь incCount может использовать полный диапазон целочисленных битов, поскольку он будет рассматриваться как число без знака.

1 голос
/ 28 мая 2011

Обратите внимание, что это не стандартное среднее значение. Стандартное среднее значение будет:

Averageusage = TotalUsage / ++incCount

Предполагая (в идеале), что incCount является некоторым полезным периодически увеличивающимся значением (например, в секундах).

Среднее затухание обычно реализуется в следующем виде: http://donlehmanjr.com/Science/03%20Decay%20Ave/032.htm, что, если я правильно перевёл:

AverageUsage = TotalUsage / (incCount+1) + incCount/(incCount+1) * AverageUsage;
incCount++;

Как упоминал Химадри, это, вероятно, следует делать в арифметике с плавающей запятой.

0 голосов
/ 29 мая 2011

Действительно ли вам / нужно / скользящее среднее или вы можете использовать какой-нибудь другой фильтр нижних частот? Однополюсный (иногда называемый «альфа») фильтр может подойти вам:

new_output = alpha * previous_output + (1-alpha)*new_input;
previous_output = new_output;

, где alpha находится в диапазоне от 0 до 0,9999 ....

Чем ближе alpha к 1, тем «медленнее» фильтр

Вы можете сделать это для простоты с плавающей запятой или целыми числами довольно просто.

0 голосов
/ 28 мая 2011

Если это предсказуемо и допустимо для TotalUsage

Если TotalUsage long long, unsigned long long, либо double.

Даже при приведении типа, если TotalUsage

Таким образом, окончательный вывод заключается в том, что TotalUsage

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...