Знаковое деление без знака

Я пытаюсь вычислить скользящее среднее, и, чтобы попытаться получить и оптимизировать немного, я упростил расчет, поэтому есть только одно деление. Когда значение уменьшается, есть точка, в которой текущее значение опускается ниже среднего. В этот момент средний скачок. Я предполагаю, что это потому, что деление без знака, а знак моего числителя интерпретируется как массивное беззнаковое число. Я просто не уверен, где мне нужно бросить 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;

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

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

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

есть ли простое решение для этого, что, надеюсь:

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

спасибо!

5 ответов


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

Использовать Математику С Плавающей Запятой

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

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

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

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

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

оба числителя / знаменателя подписаны

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

оба числителя / знаменателя не имеют знака

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


общее правило c binary ops (включая разделение) заключается в том, что операнды будут преобразованы в один и тот же тип, который является одним из: 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, тогда гипс не имеет эффект -- вычитание будет преобразовано в signed int, а затем обратно в unisgned int, и будет сделано беззнаковое деление. Если вы хотите подписанное подразделение, вам понадобится:

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

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

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


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

Averageusage = TotalUsage / ++incCount

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

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

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

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


если он предсказуем и действителен для TotalUsage

Если TotalUsage long long, unsigned long long или double было бы уместно.

даже при литье, если TotalUsage

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

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


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

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

здесь alpha от 0 до 0.9999....

ближе alpha равно 1," медленнее " фильтр

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