2011-05-27 18 views
7

Próbuję obliczyć średnią kroczącą, a aby spróbować i uzyskać i zoptymalizować nieco, uprościłem obliczenia, więc istnieje tylko jeden podział. Kiedy wartość maleje, istnieje punkt, w którym aktualna wartość jest obniżana do wartości niższej niż średnia. W tym momencie średnia liczba skoków. Wyobrażam sobie, że dzieje się tak dlatego, że podział jest niepodpisany, a bit znaku licznika jest interpretowany jako masywna liczba bez znaku. Po prostu nie jestem pewien, gdzie muszę rzucać niepodpisane, aby upewnić się, że problem nie pojawia się ponownie.Podpisany podział z unsigned licznikiem

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

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

AverageUsage zawsze będzie dodatni, ale kiedy TotalUsage spadnie poniżej AverageUsage, nie jestem pewien, czego się spodziewać z podziałem

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

ustawi licznik do podpisanej, ale nie jestem pewien jak podział się pojawi.

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

powinien działać (mogę zagwarantować wynik tej operacji pełnego nigdy nie będzie ujemny), ale martwię się o przypadkach gdy incCount osiąga wartość, że „wygląd” ujemną.

Czy istnieje proste rozwiązanie do tego, że z nadzieją:

  • nie potrzebuje if
  • Nie wymaga QWORDs

Dzięki!

+3

Byłoby pomocne, gdyby dołączyć deklarację wszystkich tych zmiennych. Zasady promocji C zależą od typów różnych podwyrażeń. Na przykład, czy AverageUsage int? unsigned int? Niepodpisany krótki? itp. – Nemo

+0

Jestem podejrzliwy wobec tego kodu; czy jesteś pewien, że jest to poprawne arytmetycznie i oblicza "średnią kroczącą", a nie "łączną średnią"? Średnia krocząca wymagałaby bufora "ostatnich wartości". – Clifford

+0

@ Clifford. Jest to podstawowe IIR. Prawdopodobnie myślisz o integratorze-grzebieniu FIR; który jest równoważny statystycznej średniej próbki (running/rolling). Bez względu na to, są one poprawne; jako filtry dolnoprzepustowe i przybliżenia do średniej populacji. –

Odpowiedz

4

Masz 2 opcje.

Zastosowanie matematyki zmiennoprzecinkowej

Chyba, że ​​chcesz to zrobić, aby uzyskać odpowiednią średnią i tak.

Nie ma czegoś takiego jak mieszany przestawny przestawny/całkowity. Tak więc zarówno licznik, jak i mianownik zostaną zamienione na zmiennoprzecinkowe.

To, czy licznik lub mianownik jest podpisany, czy nie, nie ma znaczenia. Nie ma czegoś takiego jak niepodpisany zmiennoprzecinkowy. Wartość mianownika incCount zostanie zamieniona na liczbę zmiennoprzecinkową i zostanie wykonany pełny podział zmiennoprzecinkowy.

podział Korzystanie Integer i obsłużyć szczególne przypadki

Jeśli z jakiegoś powodu chcesz pozostać z podziałem liczby całkowitej, a następnie zarówno licznik i mianownik muszą być takie same podpisany/typ unsigned.

zarówno licznik/mianownik są podpisane

incCount zostanie przekonwertowany do podpisanej liczby. Jeśli jest zbyt duży, będzie wyglądał jak liczba ujemna, a twoja odpowiedź będzie błędna. Musisz przetestować ten przepełnienie.

zarówno licznik/mianownik zostaną podpisane

Musisz zrobić licznik unsigned i używać if() oświadczenie obsłużyć dwa przypadki: TotalUsage < AverageUsage i TotalUsage > AverageUsage. Tutaj incCount może używać pełnego zakresu bitów całkowitych, ponieważ będzie traktowany jako liczba bez znaku.

+0

OK ma sens. Chcę podziału całkowitego, ponieważ śledzę wykorzystanie pamięci (w bajtach), które prawie zawsze będzie w zakresie 50 + MB. Ułamki bajtów nie martwią. Pracuję również nad ARM bez FPU. – Gdogg

1

Pamiętaj, że nie jest to średnia standardowa. Średnia standardowa to:

Averageusage = TotalUsage/++incCount 

Zakładając (najlepiej), że incCount jest jakąś użyteczną okresowo rosnącą wartością (np. Sekundy).

średnia rozkładających jest zazwyczaj realizowane bardziej jak: http://donlehmanjr.com/Science/03%20Decay%20Ave/032.htm który jeśli mam przetłumaczone poprawnie jest:

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

Jak wspomniano Himadri, powinny prawdopodobnie być wykonywane w arytmetyce zmiennoprzecinkowej.

+0

Próbowałem zminimalizować liczbę wymaganych działów. Moja formuła jest twoim uproszczeniem. – Gdogg

+0

@Gdogg: Chyba że masz jakieś dowody eksperymentalne sugerujące, że jest to hotspot, zdecydowanie sugeruję, że robisz przedwczesną optymalizację. Przy użyciu prawidłowego, standardowego algorytmu użytkownicy będą bardziej zadowoleni, ponieważ odpowiednio odzwierciedlają to, czego ludzie oczekują, gdy zobaczą średnią. –

+0

Uproszczenie to nie tylko wydajność. Twoja ekspresja w praktyce łamie się; '(incCount/(incCount + 1))' jest zawsze zerem w arytmetyce całkowitej. Jeśli przestawisz się na '(incCount * AverageUsage)/(incCount + 1)', ryzykujesz przepełnienie w liczniku. –

5

Ogólna zasada ops binarnych C (w tym podziale) jest to, że argumenty będą zarówno przekształcić do tego samego rodzaju, co jest jednym z: int, unsigned int, long, unsigned long, intmax_t, uintmax_t, float, double, long double . Jeśli oba operandy są typu z tej listy, oba zostaną przekonwertowane na późniejszy. Jeżeli nie znaczy, że będziemy zarówno być przekształcony int

Więc w przykładzie:

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

jeśli incCount jest unsigned int, wówczas obsada nie ma wpływu - w odejmowania będą konwertowane do podpisanego int i następnie wróć do unisgned int, a unsigned division zostanie wykonane. Jeśli chcesz podpisaną podział, musisz:

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

które jak można zauważyć, może Ci w kłopoty jeśli incCount przekracza INT_MAX.

Ogólnie instrukcje procesora dla dzielenia określają tylko jeden typ, który jest używany dla obu argumentów. Kiedy istnieje specjalna instrukcja podziału na różne typy, zwykle jest to dla większej (podwójnej szerokości) dywidendy, a nie innej sygnatury.

0

Jeśli jest przewidywalny i ważny dla aplikacji TotalUsage < AverageUsage, to jest całkowicie niewłaściwe, aby zmienne te były typu niepodpisanego. TotalUsage < AverageUsage oznaczałoby, że AverageUsage może następnie być ujemna (co byłoby wynikiem jeśli TotalUsage < AverageUsage. Jeśli dane są „uśrednione” nigdy nie jest ujemny, to arytmetycznie niemożliwe TotalUsage < AverageUsage aby mogło być prawdziwe.

Jeśli TotalUsage < AverageUsage jest niepoprawna, to, aby była prawdziwa, wskazywałaby na błąd w kodzie lub na przepełnienie arytmetyczne, możesz zabezpieczyć się przed tą możliwością za pomocą assert, być może zaimplementowaną jako makro, które zostało usunięte w kompilacji wydania. Jeśli wystąpi potwierdzenie, dane wejściowe są nieprawidłowe lub wystąpiło przepełnienie, w drugim przypadku typ danych jest zbyt mały i odpowiedni byłby albo long long, unsigned long long lub double.

Nawet przy odlewaniu, jeśli średnia liczba użytowników < jest równa, wynik wyrażenia jest arytmetycznie ujemny, ale ostatecznie przypisany do typu bez znaku, więc wynik będzie nadal nieprawidłowy.

Ostatecznym wnioskiem jest, że TotalUsage < AverageUsage nigdy nie może być prawdziwe, lub twoje dane mają niewłaściwy typ. Rozwiązanie prawie na pewno nie jest typem odlewu.

Moja rada jest ogólnie do zawsze używać typu podpisanego dla zmiennych, dla których zostanie przeprowadzona arytmetyka. Dzieje się tak dlatego, że semantyka języka mieszanej, podpisanej/niepodpisanej arytmetyki jest nieco tajemnicza i łatwa do zinterpretowania, a ponieważ operacje pośrednie mogą generować inne wartości ujemne. Nawet jeśli wartość ujemna dla zmiennej jest semantycznie bez znaczenia, nadal zalecałbym stosowanie podpisanych typów we wszystkich przypadkach, w których dodatni zakres tego typu pozostaje wystarczający, aby uniknąć przepełnienia, a gdy nie jest wystarczający. aby użyć większego typu, gdzie to możliwe, zamiast uciekać się do typu bez znaku tego samego rozmiaru. Ponadto, gdy wymagane są operacje arytmetyczne na niepodpisanych typach, wszystkie operandy powinny być niepodpisane (w tym literały), a żadna pośrednia operacja nie powinna skutkować przepełnieniem lub niedopełnieniem.

0

Czy rzeczywiście/potrzebujesz/średnia krocząca, czy możesz użyć innego filtra dolnoprzepustowego? Jednobiegunowej (czasami nazywany „alfa”) filtr może Ci odpowiadać:

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

gdzie alpha jest między 0 i 0,9999 ....

Im bliżej alpha jest na 1, „wolniej” filtr jest

Możesz to zrobić w postaci zmiennoprzecinkowej dla ułatwienia lub w liczbach całkowitych całkiem prosto.