Pevná řádová čárka - násobení celého čísla číslem desetinným

Ahoj. Potřeboval bych poradit s výpočty v pevné řádové čárce. Aktuálně se snažím rozchodit násobení celého čísla číslem desetinným, dejme tomu např. 853 * -0,425. Pro zobrazení desetinného čísla se běžně na 16ti bitových MCU používá formát Q15, tedy nejvyšší bit nese znaménko a následující už vlastní hodnotu. Protože počítám jak s celými čísly, tak desetinnými, chtěl jsem aplikovat formát Q15.16, kdy by informaci o znaménku nesla celá část. A teď, co jsem ne zcela pochopil: jak v tomto formátu zakódovat těch -0,425. Když nastavím MSB celé části do jedničky (= záporné číslo), a zbývající bity do nuly, nebude celá část rovna nule, ale dle dvojkového doplňku -32768. Jediné, co mě napadlo, by bylo se na dvojkový doplněk vykašlat a hold mít dvě nuly, zápornou a kladnou. Ještě mě napadlo použít strukturu s dvěma položkami: celou částí a desetinnou částí, kdy by každá z nich měla znaménkový bit. Nemáte někdo více zkušeností s pevnou řádovou čárkou, každá informace dobrá. Děkuji moc.

:arrow_right: administrator: přesunuto z "Elektronika s mikrokontroléry, procesory"

:arrow_right: administrator: přesunuto z "Pevná řádová čárka"

Ono to je docela jednoduché. Počítej s desetinným číslem úplně stejně jako by to bylo celé číslo, stačí jen vědět, že nevyjadřuje jednotky, ale binární zlomky.

Například, na desetinou část si vyhradím 1 bajt, na celou část 3 bajty, číslo zabere dohromady 4 bajty. Je to jako když desetinné číslo vynásobíš řádem zlomkové části, tedy číslem 256. Např. číslo 5 se zapíše jako 0x00000500, číslo 0.2 jako 0.2 * 256 = 51 = 0x00000033. Záporná čísla jsou opět stejná jako u běžných čísel, jen je to opět vynásobené řádem zlomkové části. Tedy např. -5 je 0xFFFFFB00 (tj. jako 0 - 5). Nejvyšší bit vyjadřuje tedy negativní číslo, ale pracuje se s ním stejně jako v běžné celočíselné aritmetice, tj. nijak zvlášť se neobsluhuje a je jen výsledkem běžných celočíselných operací. Proto je platné číslo i hodnota 0x8000… (tedy jako maximální možné záporné číslo).

Sčítání a odečítání čísel se dělá jako by to bylo celé číslo - tj. postupuje se od nejnižších řádů (bajty, slova, to co procesor umí), ty se sčítají/odčítají a provádí se přenos do vyšších řádů. Vůbec při tom není důležité kde je desetinná čárka, na algoritmu to nic nemění.

Násobení se dělá stejně jako u ručního násobení - tj. berou se jednotlivé části, násobí se mezi sebou a výsledky se přičítají do střadače. Teď nevím jistě jak se znaménkem, já to myslím řešil tak, že jsem převedl obě čísla na kladná a pak příp. negoval výsledek. U násobení se musí počítat s tím, že se zvyšuje rozsah a přesnost čísla 2x. To znamená, počítá se opět jako s celým číslem (tj. nerozlišuje se kde je desetinná tečka), ale výsledek se ukládá to bufferu, který je 2x větší, tedy desetinná část má v příkladu 2 bajty a celá 6 bajtů. Pak se převede výsledek do běžného bufferu čísla tak, že nižší polovina desetin a vyšší polovina celé části se ignoruje, použije se tedy jen ta část kolem desetinné čárky. Při násobení proto není třeba počítat přenosy přetékající rozsah nahoru, ale kvůli efektivnosti algoritmu je lepší počítat plný rozsah. Desetiny je potřeba počítat v plném rozsahu, protože ta vypouštěná část může ovlivnit výsledek kvůli zaokrouhlování.

Násobení lidé někdy počítají bitově, tj. bere se bit po bitu a přičítá rotované druhé číslo, ale když procesor umí hardwarově násobit tak by byla škoda to nevyužít, je efektivnější násobit úseky čísla podle možností procesoru.

U dělení je už horší situace. Snadnější varianta je, když se dělí číslem, které ještě zvládá hardwarová dělička procesoru. Pak se dá postupovat od vyšších řádů čísla, vzít část čísla, vydělit dělitelem a zbytek použít jako přenos do nižšího řádu. Znaménko je opět možné ošetřit převodem na absolutní hodnoty a negací výsledku (ale je možné že tohle jde jednodušeji). Při tomto dělení malým číslem se nemění rozsah výsledku, lze přímo ukládat do výstupního bufferu o stejném rozsahu.

Při dělení velkým číslem, které se musí vyjádřit v plném rozsahu, se už musí provádět dělení bitově. Dělenec se převede na dvojnásobný řád přípojením nul, tj. jako když se posune do 2x vyššího řádu. Pak se porovná dělenec a dělitel, když je dělenec větší, zapíše se do výsledku bit “1” a dělitel se od dělence odečte. Pak se dělitel rotuje o 1 bit dolů. Tedy … nějak tak, na detaily kolem dělení si teď přesně nevzpomínám.

Číslo -0,425 ve vyjádření 2+2 bajty (tedy Q15.16) se pak vyjádří jako -0,425 * 65536 = 0xFFFF9334. Kdyby části čísla nebyly násobkem bajtů, postupuje se obdobně. Např. při vyjádření Q12.4 se bude počítat s čísly stejně, jako s celými čísly představujícími desetinná čísla vynásobená 16x. Sčítání a odčítání se provádí stejně jako u celých čísel. U násobení bude výsledek větší o desetinou část, tedy při tom Q12.4 je výsledek násobení ve tvaru Q24.8. Proto se musí výsledek opravit tak, že se rotuje o 4 bity dolů. U dělení se postupuje obráceně, dělence je potřeba před dělením rotovat o 4 bity nahoru a zvýšit tak rozsah na Q24.8. Pak se vydělí jako běžné celé číslo a výsledek bude jít ve správném formátu Q12.4.

Panda38: děkuji za obšírné vysvětlení problematiky, velice si vážím toho, když někdo nabídne pomocnou ruku jako Vy. Přiznám se ale, že mi není úplně jasný zápis záporného desetinného čísla, např. toho -0,425. Když se ho budu snažit vyjádřit ve formátu Q15.16, tak tu desetinnou část 9334 HEX chápu, s tím problém nemám, ale co mi nějak nesedí, jsou ty “F” v celé části. Podle mého jsou čtyři FFFF v signed formátu -1 nebo se pletu?

Na to Q15.16 se dá dívat zas jako na celé číslo, je jedno že část představuje desetiny a část celá čísla. Vypočte se -0,425 * 65536 = -27853 (zaokrouhleno) a to se vyjádří jako 32bitové číslo 0xFFFF9334 a dělají se s tím i běžné celočíselné operace, jen se ví, že to číslo ve výsledku nepředstavuje jednotky, ale zlomek 1/65536. Tedy jakoby to byly desetiny pro uživatele, ale pro program to jsou celočíselné počty zlomků 1/65536.

A ano, 0xFFFF je číslo -1, ale protože je od 16. bitu, tak je větší * 65536, to znamená -65536 a to má hex hodnotu 0xFFFF0000. Takže to odpovídá součtu: 0xFFFF9334 = 0xFFFF0000 + 0x00009334 = -1*65536 + 37684 = -27852 = -0,425 * 65536.

Pěkný den,

tak už se v tom začínám orientovat, děkuji za obšírné vysvětlení a trpělivost.

Pro kontrolu ještě jdno číslo: -3,875 převedu na 0xFFFC2000, jestli se nepletu. Neboli -3,875 * 65536 = -253952, pak 2^32 - 253952 = 0xFFFC2000. 0xFFFC = -4; 0x2000/65536 = 0,125; tudíž -4 + 0,125 =
-3,857. Tak snad už to mám dobře.

Ano