Elektroda.pl
Elektroda.pl
X

Wyszukiwarki naszych partnerów

Wyszukaj w ofercie 200 tys. produktów TME
Kategoria: Kamery IP / Alarmy / Automatyka Bram
Montersi
Proszę, dodaj wyjątek elektroda.pl do Adblock.
Dzięki temu, że oglądasz reklamy, wspierasz portal i użytkowników.

jak zmienić obliczenia z ułamkami na liczby całkowite w C ?

mototest 11 Sie 2006 22:06 2617 19
  • #1 11 Sie 2006 22:06
    mototest
    Poziom 19  

    Witam

    chciałbym przyspieszyć działanie programu przez wyeliminowanie liczb zmiennoprzecinkowych jesli to bedzie możliwe, mam taki wzór który wylicza procent z dowolnej liczby od 0-100% wzór wygląda tak :
    wynik = (iczba_całkowita*ofset)+2000 gdzie ofset jest ułamkiem od 0 do 1 ze skokiem co 0.0007 czyli 819 pozycji (gdyby to miało jakieś znaczenie) ale generalnie chce liczyć procent z "liczby" ale wynik jest liczbą załkowitą , pomóżcie jak to zrobić wykorzystując tylko liczby całkowite , dokładnie chodzi o to że mam tablicę z sinusem i musze przeliczać na żywo jego amplitudę od wzorcowej (0-4500mV) do wzorcowej -1000mV (czyli 0-3500mV)
    gdzie ten 1000mV musze podzielić na 819 części bo tyle mi wyciągnie DAC 12 bitowy rozdzielczości

    dzięki
    Bogdan

  • #2 12 Sie 2006 02:09
    sawitar
    Poziom 15  

    Najprościej to oczywiście napierw pomnożenie przez licznik i następnie podzielenie przez mianownik, ale tego juz pewnie próbowałeś.
    Może spróbuj zastosować kodowanie Q. Dla 819 wartości potrzebowałbyć Q10, pozostaje ci 6 bitów na część całkowitą bez znaku ( zakładam ze twój int ma 16 bitów )

    Pozdrawiam.

  • #3 12 Sie 2006 11:30
    mototest
    Poziom 19  

    nie wiem na czym polega to kodowanie Q, możesz dać jakiegoś linka gdzie jest to opisane ?

  • #4 12 Sie 2006 11:58
    Xitami
    Poziom 29  

    Czasem musimy podzielić wartość zmiennej przez stałą, która NIE JEST całkowitą potęgą dwójki. Można chytrze wykorzystać wbudowaną w procesor mnożarkę. A nawet bez mnożarki sposób jest dobry, bo dzielenie jest przynajmniej dwa razy dłuższe i wolniejsze od mnożenia.
    Przyjmijmy, że chcemy dzielić bajt X przez stałą D.
    Znajdźmy „k”, największą naturalną potęgę dwójki, mniejszą od D. k = 2 ** n. Np. jeżeli będziemy dzielić przez 10, to k=8, a n=3. Policzmy t= 256*k/D. Wynik zaokrąglamy, jeżeli część ułamkowa „t” była mniejsza niż 0,5 zapamiętajmy ten fakt, bo będzie konieczna poprawka - zwiększenie X o jeden.

    X / D == hi(X’*t) >> n. // hi() – starszy bajt

    Przykład w asemblerze na AVR. Dzielimy powiedzmy przez 10.
    2**4=16 za dużo, 2**3=8 OK. bo 8<10, czyli k=8, a n=3. 256*k=2048, 2048/D=204.8, czyli t=205, część ułamkowa 0.8 > 0.5, czyli poprawka nie jest konieczna.

    Code:
       ;r1=r30 div 10
    
       ldi   r31, 205   ; r31:= t
       mul   r30,r31      ; r1:r0:=r30*r31, r1=hi(r30*t)
       lsr   r1      ; logiczny shift w prawo
       lsr   r1
       lsr   r1      ; r1:= r1>>3
    Razem 5 instrukcji, 6 cykli zegara. W notce aplikacyjnej ATMELa „avr200.asm” znajdziesz procedurę dzielącą przez siebie dwie ośmiobitowe liczby bez znaku, 14 rozkazów, 97 cykli. Na Atmelku bez mnożarki, mnożenie można załatwić 9 rozkazami w 58 cykli.
    Drugi przykład, D=7. k=4, n=2, 256*k=1024, 1024/7=146.286, t=146, 0.286<0.5 czyli konieczna poprawka.
    Code:
       ;r1=r30 div 7
    
       inc   r30   ;r30+=1, poprawka, lecz uwaga 255 div 7 da zero
       ldi   r31,146
       mul   r30,r31
       lsr   r1
       lsr   r1
    Czyli, w przypadku poprawki działa z ograniczeniem. Poprawki konieczne są dla dzielników: 7, 11, 14, 21, 22, 23, 28, 29, 31, 33 i wielu jeszcze. Lecz sztuczka i tak jest użyteczna.
    Łatwo uogólnić ją dla liczb ze znakiem (mulsu zamiast mul, asr zamiast lsr), albo dla liczb 16 bitowych.

    Podobnie można usprawnić mnożenie bajtu przez ułamek: u=x * (L / M). Możemy oczywiście najpierw pomnożyć przez L, następnie podzielić (oczywiście mnożąc) przez M. Lecz można to zrobić w jednym kroku. Zakładam, że L/M < 1, czyli L < M. Szukamy takiego K=2**N, że L/M*K < 256 oraz część ułamkowa L/M*K < 0.5. Obliczamy wg wzoru: u=[x*(L/M*K)] >> N. Gdyby zapomnieć o zaokrąglaniu to problem rozwiązany. Jeżeli chcemy wynik mieć zaokrąglony to stosujemy formułkę u= ([x + (M/L/2)] * [L/M*K]) >> N. Czyli x musi być mniejsze od 256 - (M/L/2).
    Code:
       ; r1= r30 * (15/87)
    
       subi   r30,(-3)   ; r30:=r30 - (-M/L/2)
       ldi   r31,88      ; N=9, K=512, L/M*K=88
       mul   r30,r31
       lsr   r1      ; lsr powtarzamy N-8 razy

    Może to cię czymś natchnie?

  • #5 12 Sie 2006 13:10
    mototest
    Poziom 19  

    z tym że ja mam zupełnie inne liczby i inne zakresy
    tablicę mam zapełnioną 32 liczbami 0d -1600 do 1600 i potrzebuję to liczyć tak : wynik=(liczba_z_tablicy*amplituda)/819 gdzie amplituda jest od 0 do 819 ,już przy normalnym liczeniu procek mi coś źle działa mimo że wynik jest typu long to 1600*819 procek liczy źle ,powinno być 1.310.400 a procek daje 4294966976 nie wiem skąd, do pewnego zakresu liczb liczy dobrze potem wysiada, chyba że printf mam źle ?? printf("wynik=%lu \n",test); wynik wysyłam na RS232 ,ale raczej nie bo jak zamiast tego jeszcze zrobię (1600*819)/819 co powinno dać 1600 ,to proc wylicza 0, skąd to sie bierze??? zaokrąglanie nie jest mi potrzebne

    OK z błędem sobie poradziłem ,zmieniłem wszystkie dane na long i liczy dobrze chociaż wydaje mi się to dziwne żeby nie mógł mnożyć róźnych typów danych ...albo coś jest z tym printf

  • #6 12 Sie 2006 15:37
    Xitami
    Poziom 29  

    1600*819 = 1’310’400 = 0013’FEC0 hex
    4’294’966’976 = FFFF’FEC0 hex
    FEC0 hex
    1310400 ≡ 4294966976 (mod 65536)
    printf("wynik=%lu \n",test); a jakiego typu jest zmienna „test”? pewnie ma tylko 16 bitów.

    Jeżeli możesz pozwolić sobie na roztrwonienie 1’640 bajtów to może tak:
    unsigned amplitudy[820] = { 0, 40, 80, 120, 160, 200, 240, 280 ..... }
    albo
    for (i=0; i<=819; i++)
    amplitudy[i]=i*32768/ 819; // (i<<15) / 819; //to jest właśnie Q15
    ...
    wynik=(liczba_z_tablicy * amplitudy[amplituda]) >> 15; // potrzebne tylko mnożenie 16*16=32 i jedno dodawanie :D

  • #7 12 Sie 2006 15:58
    mototest
    Poziom 19  

    :) bardzo ciekawy sposób
    będę testował dopiero za tydzień bo jadę na urlop, dzisiaj pobawię się jedynie na symulatorze programowym, zmienną test zrobiłem long ,ale próbowałem też double i bez zmian... teraz trochę poprzekształcałem program że jest dobrze, przypisałem wyniki dużych liczb do małych zmiennych.
    Na trwonienie bajtów nie mogę sobie pozwolić , muszę poprostu wyciągnąć 819 pozycji amplitudy czyli co jeden bajt , w tablicy spłaszczam maksymalną amplitudę o wartości 3932 w maksymalnym punkcie do 3932-819 czyli 3113 (minimum) co daje 1000mV różnicy przy moim przetworniku 12 bitowym i Vref 5000mV , więc muszę zmienną "amplituda" zmieniać od 0-819 co daje zmianę bajtów na przetwornik DAC od 3932 do 3113 w maksymalnym punkcie.

  • #8 12 Sie 2006 16:06
    sawitar
    Poziom 15  

    Wszystkie przykłady dla liczb 8 bitowych bez znaku.

    Dla liczb zapisanych w naturalnym kodzie binarnym:
    - bit 0 ma wartość 1 lub 0 ( bo 2^0 =1 )
    - bit 1 ma wartość 2 lub 0 ( bo 2^1 =2),
    - bit 2 ma wartość 4 lub 0 ( bo 2^2 =4 )
    - itd
    Z tąd liczba 5 ma postać 0000$0101. Jak do tąd wszystko po staremu. Zapis taki nazwijmy I8Q0. Można w nim zapisać liczby z zakresu 0-255 ze skokiem co 1. Niestety nie da się w nim zapisać ułamków.

    Teraz załózny ze wartość najmłodszego bitu wynosi nie 1 lub 0 tylko 0.5 lub 0. Pozostałe wartości zmniejszamy analogicznie :
    - bit 0 ma wartość 0.5 lub 0 ( bo 2^(-1) =0.5 )
    - bit 1 ma wartość 1 lub 0 ( bo 2^ 0 =1 )
    - bit 2 ma wartość 2 lub 0 ( bo 2^ 1 =2 )
    - itd.
    Z tąd liczba 5 ma postać 0000$1010. Zapis taki nazwijmy I7Q1. Można w nim zapisać liczby z zakresu 0-12755 ze skokiem co 0,5 np 5,5= 0000$1011

    W analogiczny sposób można tworzyć kodowania o większej rozdzielczości w części ułamkowej, kosztem oczywiście części całkowitej.

    Konwersja pomiędzy róznymi kodowaniami polega na odpowiednim przesówaniu bitów w lewo lub w prawo:
    I8Q0 << 1= I7Q1 ( następuje zmniejszenie zakresu części całkowitej )
    I2Q6>>2=I4Q4 ( następuje zmniejszenie precyzji części ułamkowej )

    Operacje arytmetyczne na takich liczbą są identyczne jak na liczbach w naturalnym kodzie binarnum ( czyli I8Q0 ), z tym ze dodawać i odejmować można liczby zapisane tylko w tym samym kodzie.
    I2Q6 + I2Q6 = I2Q6 ( wynik poprawyny )
    I2Q6 + I3Q5 = błąd

    Mnożyć można liczby w różnych kodach ale wynik bedzie zapisany w kodzie będącym sumą poszczególych kodów:
    I2Q6 * I2Q6 = I4Q12
    I3Q5 * I2Q6 = I5Q11

    Tak więc dla twojego zastosowania możesz użyć liczb 32 bitowych i kodowania I20Q12, co gwarantuje ci 4k wartości dla części ułamkowej i spory zakres zmnienności dla części całkowitej.

    Pozdrawiam.

  • #9 12 Sie 2006 16:13
    mototest
    Poziom 19  

    No jeszcze jedno , w moim procku nie miesci się tablica o 820 pozycjach...
    chyba zostaje przeliczanie na żywo... stosuję ADUC842 z rdzeniem 8052
    mieszczę jedynie tablicę z wyliczonym sinusem o 32 pozycjach i trochę zmiennych, już stosuję dwie inne tablice (z sinusami wzorcowymi) ale umieszczam je we flashu, co spowalnia mi sinusa. Chcę wygenerować sinusa 10kHz a na razie wyciągnąłem 7.5Khz między innymi przez obliczenia na zmiennoprzecinkowych i czytanie wzorca do przeliczeń z flasha zamiast z RAMu

  • #10 12 Sie 2006 16:52
    sawitar
    Poziom 15  

    Przebieg sinusoidalny możesz generować rekurencyjnie. Sposób ten wymaga pamiętania bodajże 2 ostatnich wygenerowanych próbek, no i kilku bajtów na obliczenia.

    Co właściwie robisz ?

  • #11 12 Sie 2006 17:08
    mototest
    Poziom 19  

    potrzebuję generować sinusy przeciwne w fazie do potencjometra indukcyjnego, gdzie z jednej strony wchodzi wzorcowy z drugiej strony regulowany, a z suwaka czytam napięcie i doprowadzam ją do minimalnej wartości zmieniając amplitudę jednego z sinusów ,ale musze mieć dużą dokładność ,najlepiej 1mV na podstawie amplitudy tego sinusa regulowanego odczytuję wortość suwaka potencjometru, nie za bardzo widzę jak wykorzystać praktycznie to kodowanie Q chociaż go zrozumiałem. może masz jakiś sposób inny na szybką zmianę tej amplitudy wzorcowej inną niż ja stosuję ?? do tej pory testowałem dwa wzory :

    Code:
    for (k=0;k<33;k++) DAC[k] = ((sinus_wzorcowy[k]*ofset))+przesuw_od wzorca ; 
    gdzie ofset jest co 0.0007 co daje 819 pozycji i drugi :
    Code:
    for (k=0;k<33;k++) DAC[k] = ((sinus_wzorcowy[k]*ofset)/819)+przesuw_od_wzorca ; 
    gdzie ofset jest w zakresie 0 to 819

    wzorzec jest z 32 próbek 0d -1600 do 1600.
    przesuw od wzorca =2300

    ten drugi jest na pewno szybszy, ale na pewno można to jakoś jeszcze przyspieszyć, czym większa częstotl. sinusów tym większa dokładność odczytu, w fabrycznych rozwiązaniach stosują 10kHz

  • #12 12 Sie 2006 19:03
    sawitar
    Poziom 15  

    S=sinus_wzorcowy <1600;-1600> - mieści sie na 12 bitach
    O=offset <0;1> co najmniej 819 wartości - 10 bitów
    P=przesuw 2300 - stała -> dowolne kodowanie ( niech to bedzie I32 )
    W=wynik ma być całkowity ( niech to bedzie I32 )

    1. S zapisujemy w kodzie I32 ze znakiem ( signed long )
    2. O zapisujemy w kodzie I20Q12 ( tutaj potrzebne jest małe przekształcenie )
    3. dokonujemy mnożenia S*O wynik bedzie w kodzie I52Q12, ponieważ zmienna jest tylko 32 bitowa nastąpi obcięcie do I20Q12, ze względu na zakres zmienności danych wejściowych bity te i tak zawsze mają wartość 0, a wiec nic nie tracimy
    4. wynik przekształcamy do kodu I32 ( przesuwamy w prawo o 12 miejsc )
    5. dodajemy przesuw_od_wzorca
    6. wynik jest w kodzie I32 czyli w naturanym kodzie binarnym

    Pozdrawiam.

  • #13 12 Sie 2006 19:18
    mototest
    Poziom 19  

    czyli patrząc na pierwszy rzut oka zastosowałeś mój pierwszy wzór z offsetem co 0.0007 tyle że zamiast float użyłeś Q10 ,czy się mylę ?
    ale wydaje mi się że tego nie zrobię sam...

  • #14 12 Sie 2006 19:26
    sawitar
    Poziom 15  

    Dokładnie tak.

    Zrobiłem symulacje błędu i średnie odchylenie zapisu offsetu wynosi 0,00012 (co daje 0,105% ) oczywiście jest dużo większe dla bardzo małych wartości typu 0,0007 ( błąd 30 % ) lub 0,0014 ( błąd 12 %, ale mniej więcej od 0,005 jest juz pomijalnie mały.

    Znaczy się prawie tak.
    Użyłem Q12. Pozatym dla zakresu <0;1) ze skokiem co 0,0007 jest 1428 wartości

  • #15 12 Sie 2006 22:48
    mototest
    Poziom 19  

    to może dasz wzór takiej jednej pętli z moimi wartościami ??, bo mimo że rozumiem system to nie potrafię zatrybić jak to wstawić do wzoru bez jakiegoś praktycznego przykładu ... poza tym widzę ten system binarnie ale dziesiętnie już nie. bo przecież nie będę przeliczał każdej liczby z bin na dec??

    Dodano po 27 [minuty]:

    no zrozumiałem (tak mi się wydaje) że ofset to będą liczby w systemie Q10 od 1 do 1418 które mają wartość od 0,0007 do 1 ,tylko potem trzeba to jakoś przekonwertować ,ale tego już na razie nie wiem jak

    Dodano po 1 [godziny]:

    zrobiłem taką próbę :
    uznałem że 1428 jest to to samo co 1 ze względu na 1428*0.0007

    teraz pomnożyłem 1600 (z tablicy) * 1428 (czyli 1) = 2284800 teraz przesunąłem to w prawo 12 razy i wyszło 557 a powinno 1600, gdzie robię błąd ?

  • #16 13 Sie 2006 06:03
    sawitar
    Poziom 15  

    Weźmy taki przykład: 1600 * 0,054 = 86,4
    kody Q : I32Q0 * Q12 = I32Q12
    kody liczb: 1600 * 221 = 353600
    teraz dokonam konwersji z I32Q12 do I32Q0
    353600 >> 12 = 86

    tak dla jasności 0,054 w kodzie Q12 to 221
    a 86,4 w kodzie Q32I12 to 353600

  • #17 13 Sie 2006 11:41
    Smoczy
    Poziom 18  

    a wypróbuj taki zapis: wynik=((long)liczba_z_tablicy*amplituda)/819 i powioedz, czy procesor dokonał poprawnych obliczeń.

  • #18 13 Sie 2006 13:49
    sawitar
    Poziom 15  

    To mu działa. Ale chce wyeliminować obliczenia zmiennoprzecinkowe ze względu za prędkośc wykonywania programu

  • #19 13 Sie 2006 20:16
    mototest
    Poziom 19  

    wrócę do tematu za tydzień bo wyjeżdżam na urlop
    na razie dzięki za pomoc

    pozdrawiam

  • #20 22 Sie 2006 13:01
    mototest
    Poziom 19  

    OK z urlopu wróciłem ,ale już sobie poradziłem z tym problemem
    wymyśliłem wzór :
    test =((sin[k]*ofset)/1638)+2036; gdzie ofset jest od 819 do 1638
    2036 to przesunięcie w osi napięcia
    wtedy próbki z tablicy zmieniają się co jeden w zależności od ofsetu i wszystko jest liczone na liczbach całkowitych

    dzięki za pomoc

 Szukaj w ofercie
Zamknij 
Wyszukaj w ofercie 200 tys. produktów TME