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