2011-05-27 27 views
7

Estoy tratando de calcular una media móvil, y para tratar de obtener y optimizar un poco, he simplificado el cálculo por lo que solo hay una división. Cuando el valor está disminuyendo, hay un punto donde el valor actual se reduce a menos que el promedio. En este punto, el promedio salta. Imagino que esto se debe a que la división no está firmada, y el bit de signo de mi numerador se interpreta como un número masivo sin firmar. Simplemente no estoy seguro de dónde tengo que lanzar sin firmar para asegurarme de que este problema no vuelva a aparecer.División firmada con el numerador sin signo

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

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

AverageUsage siempre será positivo, pero cuando cae por debajo de TotalUsage AverageUsage, no estoy seguro de qué esperar de la división

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

fijará el numerador a firmado, pero no estoy seguro cómo ocurrirá la división

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

deben trabajar (Puedo garantizar que el resultado de esta operación completa nunca será negativo), pero estoy preocupado por los casos en que incCount alcanza un valor que 've' negativo.

¿Hay una solución simple a este que se espera:

  • no necesita una instrucción if
  • No requiere QWords

Gracias!

+3

Sería útil incluir la declaración de todas estas variables. Las reglas de promoción de C dependen de los tipos de las diversas subexpresiones. Por ejemplo, ¿AverageUsage es int? unsigned int? unsigned short? etc. – Nemo

+0

Sospecho de este código; ¿Estás seguro de que esto es aritméticamente correcto y calcula un "promedio móvil" en lugar de un "promedio acumulado"? Un promedio móvil requeriría un buffer de "valores recientes". – Clifford

+0

@Clifford. Es un IIR básico. Probablemente estés pensando en una FIR integradora-peine; que es equivalente a la media de muestra estadística (en ejecución/rodando). De todos modos, ambos son correctos; como filtros de paso bajo y aproximaciones a la media poblacional. –

Respuesta

4

Tiene 2 opciones.

uso de la coma flotante Matemáticas

creo que quiere hacer esto para tener un promedio adecuado de todos modos.

No existe una división mixta flotante/entero. Por lo tanto, tanto el numerador como el denominador se convertirán en un punto flotante.

Si el numerador o el denominador está firmado o no, entonces no importa. No existe el punto flotante sin signo. El denominador incCount se convertirá en un punto flotante y se realizará una división de punto flotante completo.

división de enteros Uso y manejar los casos especiales

Si por alguna razón usted desea permanecer con la división entera, tanto el numerador y el denominador tienen que ser del mismo tipo firmado/sin signo.

Tanto Numerador/Denominador están firmados

incCount será convertido a un número firmado. Si es demasiado grande, se verá como un número negativo y tu respuesta será incorrecta. Tienes que probar este desbordamiento.

numerador/denominador son sin firmar

Usted tiene que hacer el numerador sin firmar y utilizar una instrucción if() para manejar los dos casos: TotalUsage < AverageUsage y TotalUsage > AverageUsage. Aquí incCount puede usar el rango completo de bits enteros ya que se tratará como un número sin signo.

+0

Ok, tiene sentido. Quiero división de enteros ya que estoy rastreando el uso de memoria (en bytes) que casi siempre estará en el rango de 50 + MB. Las fracciones de bytes no son una preocupación. También estoy trabajando en un ARM sin una FPU. – Gdogg

1

Tenga en cuenta que este no es un promedio estándar. A media estándar sería:

Averageusage = TotalUsage/++incCount 

Suponiendo (idealmente) que incCount es algunas útiles creciente valor periódicamente (como segundos).

Un promedio de descomposición se lleva a cabo normalmente más como: http://donlehmanjr.com/Science/03%20Decay%20Ave/032.htm el que si he traducido correctamente es:

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

Como se mencionó Himadri, éstos probablemente se debe hacer en la aritmética de punto flotante.

+0

Estaba tratando de minimizar el número de divisiones requeridas. Mi fórmula es una simplificación tuya. – Gdogg

+0

@Gdogg: a menos que tenga alguna evidencia experimental que sugiera que se trata de un punto de acceso público, le recomiendo encarecidamente que realice una optimización prematura. Usar un algoritmo correcto y estándar hará más felices a los usuarios, ya que refleja adecuadamente lo que la gente espera cuando ve un promedio. –

+0

La simplificación no se trata solo de rendimiento. Tu expresión se rompe mal en la práctica; '(incCount/(incCount + 1))' siempre es cero en aritmética de enteros. Si reordena a '(incCount * AverageUsage)/(incCount + 1)', corre el riesgo de desbordamiento en el numerador. –

5

La regla general de operaciones binarias C (incluyendo la división) es que los operandos ambos serán convertidos en el mismo tipo, que es uno de: int, unsigned int, long, unsigned long, intmax_t, uintmax_t, float, double, long double . Si ambos operandos son de tipos en esa lista, ambos se convertirán al último. Si ninguno es decir, que van a ser convertidos en tanto int

Así que en su ejemplo:

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

si incCount es unsigned int, a continuación, el yeso no tiene ningún efecto - el de resta se convierte en int firmado y luego volverá a int sin editar y se realizará una división sin firmar. Si quieres una división firmado, tendrá que:

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

que como se nota puede usted tener problemas si excede incCount INT_MAX.

En general, las instrucciones del procesador para la división solo especifican un tipo, que se usa para ambos operandos. Cuando hay una instrucción especial para la división con diferentes tipos, por lo general es para un dividendo más grande (doble ancho), no una signatura diferente.

0

Si es previsible y válido para TotalUsage < AverageUsage, entonces es totalmente inapropiado que estas variables sean de tipo sin signo. TotalUsage < AverageUsage implicaría que AverageUsage entonces podría ser negativo (lo que sería el resultado si TotalUsage < AverageUsage. Si los datos que se promedió 'nunca es negativa, entonces es aritméticamente imposible TotalUsage < AverageUsage para ser verdad.

Si TotalUsage < AverageUsage no es válido, entonces para que sea verdadero, indicaría un error en su código o un desbordamiento aritmético. Puede protegerse contra esa posibilidad con una afirmación, tal vez una implementada como una macro que se elimina en una compilación de lanzamiento. Si se produce la afirmación, los datos de entrada no son válidos o se produce un desbordamiento, en este último caso, el tipo de datos es demasiado pequeño y sería apropiado long long, unsigned long long o double.

Incluso con el casting, si TotalUsage < AverageUsage es verdadero, entonces el resultado de la expresión es aritméticamente negativo, pero finalmente se asigna a un tipo sin signo, por lo que el resultado seguirá siendo incorrecto.

La conclusión final es o bien que TotalUsage < AverageUsage nunca puede ser cierto, o sus datos tienen un tipo inapropiado. La solución casi con certeza no es ningún tipo de elenco de tipo.

Mi consejo es generalmente siempre utilice un tipo firmado para las variables en las que se realizará la aritmética. Esto se debe a que la semántica del lenguaje de la aritmética mixta con signo/sin signo es algo misteriosa y fácilmente malentendida, y porque las operaciones intermedias pueden generar valores que de otra manera serían negativos. Incluso si un valor negativo para la variable carece de sentido semántico, seguiría abogando por el uso de tipos firmados en todos los casos en que el rango positivo de dicho tipo siga siendo suficiente para evitar el desbordamiento, y cuando no sea suficiente. para usar un tipo más grande donde sea posible en lugar de recurrir a un tipo sin signo del mismo tamaño. Además, cuando se requieren operaciones aritméticas en tipos sin signo, todos los operandos deben estar sin firmar (incluidos los literales), y ninguna operación intermedia debe dar como resultado un subdesbordamiento o desbordamiento.

0

¿Realmente/necesita/un promedio móvil, o puede utilizar algún otro filtro de paso bajo? Un solo polo (a veces llamado un "alfa") filtro que podría adaptarse a:

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

donde alpha es entre 0 y 0,9999 ....

Cuanto más cerca alpha es 1, el "más lento" el filtro es

Puede hacer esto en coma flotante para mayor facilidad, o en números enteros con bastante facilidad.

Cuestiones relacionadas