2011-05-27 14 views
7

Sto provando a calcolare una media mobile e per cercare di ottenere e ottimizzare un bit, ho semplificato il calcolo in modo che ci sia solo una divisione. Quando il valore diminuisce, c'è un punto in cui il valore corrente viene abbassato a un valore inferiore alla media. A questo punto i salti medi. Immagino che ciò avvenga perché la divisione non è firmata e il bit del mio numeratore è interpretato come un massiccio numero senza segno. Sono solo non sono sicuro di dove devo lanciare unsigned per assicurare che questo problema non riappaia.Divisione firmata con numeratore senza segno

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

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

AverageUsage sarà sempre positivo, ma quando scende al di sotto TotalUsage AverageUsage, io non sono sicuro di cosa aspettarmi con la divisione

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

imposterà il numeratore di firmato, ma non sono sicuro come si verificherà la divisione.

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

dovrebbe funzionare (posso garantire il risultato di questa piena operatività sarà mai essere negativo), ma sono preoccupato per i casi in cui incCount raggiunge un valore che 'sembra' negativo.

C'è una semplice soluzione a questo che si spera:

  • non ha bisogno di un'istruzione if
  • Non richiede QWords

Grazie!

+3

Sarebbe utile se si includesse la dichiarazione di tutte queste variabili. Le regole di promozione di C dipendono dai tipi delle varie sotto-espressioni. Ad esempio, AverageUsage è un int? int unsigned? corto senza firma? ecc. – Nemo

+0

Sono sospettoso di questo codice; sei sicuro che questo sia aritmeticamente corretto e calcoli una "media mobile" piuttosto che una "media cumulativa"? Una media mobile richiederebbe un buffer di "valori recenti". – Clifford

+0

@Clifford. È un IIR di base. Probabilmente stai pensando a un FIR integratore-pettine; che è equivalente alla media campionaria statistica (in esecuzione/a rotazione). Indipendentemente da ciò, sono entrambi corretti; come filtri passa-basso e approssimazioni alla media della popolazione. –

risposta

4

Hai 2 opzioni.

Usa calcoli in virgola mobile

penso che si vuole fare questo per ottenere una media adeguata in ogni caso.

Non esiste una divisione mista floating/integer. Quindi, sia il numeratore che il denominatore saranno convertiti in un punto variabile.

Se il numeratore o il denominatore è firmato o non firmato, non importa. Non esiste una cosa come un punto di partenza senza segno. Il denominatore incCount verrà convertito in virgola mobile e verrà eseguita la divisione in virgola mobile completa.

divisione Usa interi e gestire i casi particolari

Se per qualche motivo si vuole stare con la divisione intera, allora sia il numeratore che il denominatore devono essere lo stesso tipo firmato/unsigned.

sia il numeratore/denominatore sono firmati

incCount sarà convertito in un numero con segno. Se è troppo grande, sembrerà un numero negativo e la tua risposta sarà sbagliata. Devi testare per questo overflow.

sia il numeratore/denominatore sono unsigned

Devi fare il numeratore non firmato e utilizzare un'istruzione if() per gestire i due casi: TotalUsage < AverageUsage e TotalUsage > AverageUsage. Qui incCount può utilizzare l'intera gamma di bit interi poiché verrà trattato come un numero senza segno.

+0

Ok ha senso. Voglio divisione integer perché sto monitorando l'utilizzo della memoria (in byte) che sarà quasi sempre nell'intervallo 50 + MB. Le frazioni di byte non sono una preoccupazione. Sto anche lavorando su un ARM senza FPU. – Gdogg

1

Ovviamente questa non è una media standard. Una media standard sarebbe:

Averageusage = TotalUsage/++incCount 

Supponendo (idealmente) che incCount sia qualche utile valore periodicamente aumento (come secondi).

Una media decomposizione viene tipicamente implementato più simile: http://donlehmanjr.com/Science/03%20Decay%20Ave/032.htm che se ho tradotto correttamente è:

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

Come accennato Himadri, questi dovrebbero essere probabilmente fatto in aritmetica in virgola mobile.

+0

Stavo cercando di ridurre al minimo il numero di divisioni richieste. La mia formula è una semplificazione del tuo. – Gdogg

+0

@Gdogg: a meno che tu non abbia alcune prove sperimentali che suggeriscono che questo è un hotspot, ti suggerisco caldamente che stai facendo un'ottimizzazione prematura. L'utilizzo di un algoritmo corretto e standard renderà più felici i tuoi utenti poiché riflette correttamente ciò che le persone si aspettano quando vedono una media. –

+0

La semplificazione non riguarda solo le prestazioni. La tua espressione di essa si rompe male in pratica; '(incCount/(incCount + 1))' è sempre zero nell'aritmetica dei numeri interi. Se riorganizzi a '(incCount * AverageUsage)/(incCount + 1)', rischi di overflow nel numeratore. –

5

La regola generale op binarie C (tra cui la divisione) è che gli operandi saranno entrambi convertiti stesso tipo, che è uno di: int, unsigned int, long, unsigned long, intmax_t, uintmax_t, float, double, long double . Se entrambi gli operandi sono di tipi in quell'elenco, saranno entrambi convertiti in quello successivo. Se nessuno dei due è, faranno entrambi essere convertito in int

Quindi nel tuo esempio:

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

se incCount è unsigned int, poi il cast non ha alcun effetto - la sottrazione verrà convertito in int firmato e quindi torna a unisign int e verrà eseguita una divisione non firmata. Se si desidera una divisione firmato, è necessario:

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

che, come si nota può ottenere nei guai se incCount supera INT_MAX.

In generale, le istruzioni del processore per la divisione specificano solo un tipo, che viene utilizzato per entrambi gli operandi. Quando c'è un'istruzione speciale per la divisione con tipi diversi, di solito è per un dividendo più grande (doppia larghezza), non per una firma diversa.

0

Se è prevedibile e valido per TotalUsage < AverageUsage, è del tutto inappropriato che queste variabili siano di tipo senza segno. TotalUsage < AverageUsage implicherebbe che AverageUsage potrebbe quindi essere negativo (che sarebbe il risultato se TotalUsage < AverageUsage. Se i dati 'in media' non è mai negativo, allora è aritmeticamente impossibile per TotalUsage < AverageUsage per essere vero.

Se TotalUsage < AverageUsage non è valido, per essere vero dovrebbe indicare un errore nel codice o un overflow aritmetico.Potrebbe evitare questa possibilità con un'asserzione, forse una implementata come macro che viene rimossa in una build di rilascio. Se si verifica l'asserzione, i dati di input non erano validi o si verificava un overflow, in quest'ultimo caso il tipo di dati è troppo piccolo e sarebbe opportuno un long long, unsigned long long o double.

Anche con la trasmissione, se TotalUsage < AverageUsage è impostato su true, il risultato dell'espressione è aritmeticamente negativo, ma alla fine viene assegnato a un tipo non firmato, pertanto il risultato sarà ancora errato.

La conclusione finale è che TotalUsage < AverageUsage non può mai essere vero o che i dati hanno un tipo inappropriato. La soluzione è quasi certamente non un tipo di cast di tipo.

Il mio consiglio è generalmente di sempre utilizzare un tipo firmato per le variabili su cui verrà eseguita l'aritmetica. Questo perché la semantica del linguaggio di aritmetica mista firmata/non firmata è alquanto arcana e facilmente fraintesa, e perché le operazioni intermedie possono generare valori altrimenti negativi. Anche se un valore negativo per la variabile è semanticamente priva di significato, vorrei comunque sostenere l'uso di tipi firmati in tutti i casi in cui l'intervallo positivo di tale tipo rimane sufficiente per evitare l'overflow e dove non è sufficiente. utilizzare un tipo più grande, laddove possibile, anziché ricorrere a un tipo non firmato della stessa dimensione. Inoltre, quando sono richieste operazioni aritmetiche su tipi senza segno, tutti gli operandi devono essere senza segno (compresi i valori letterali) e nessuna operazione intermedia dovrebbe risultare sotto o overflow.

0

Avete veramente/bisogno/una media mobile o potete usare qualche altro filtro passa basso? A unipolare (a volte chiamato "alpha") Filtro potrebbe adattarsi:

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

dove alpha è compreso tra 0 e 0.9999 ....

Il più vicino è quello di alpha 1, il "più lento" il filtro è

Si può fare questo in virgola mobile per facilità, o in interi abbastanza semplice.

Problemi correlati