

Pretypovanie real na integer mimo trunc - Delphi
Dobrý deň, priatelia, vedeli by ste mi, prosím, dať kvalifikovanú odpoveď na otázku, ako v Delphi 6 spoľahlivo pretypovať real alebo extended na integer? Vždy ma učili používať na to funkciu trunc, ktorá by vraj mala vrátiť celočíselnú časť z reálneho čísla. Prax mi však ukázala, že táto definícia nie je celkom správna. Neraz sa mi stalo, že moja aplikácia nepracovala celkom správne a často bol na vine práve príkaz trunc, ktorý vrátil nižšiu hodnotu najmä vtedy, ak parametrom bol nejaký výpočet alebo záporné číslo. Práve teraz tu mám relatívne jednoduchý prípad - užívateľ do editačného poľa vpíše reálne číslo, ktoré potom má byť vynásobené 1000 a ďalej už spracovávané ako celé číslo. Ak však do editačného poľa vpíšem -16.275 a urobím nasledovné operácie:
var x,y:real;i:integer;
x:= strtofloat(ansireplacetext( edit2.text,'.',',')); // prevediem si obsah edit pola na real - výsledok je -16,275;
y:=x*1000; // vynásobím 1000 - výsledok je -16275 v premennej typu real;
i:=trunc(y);
výsledkom nie je -16275 ale -16274. Takéto situácie sa mi stali už viackrát, preto hľadám spôsob, ako skutočne spoľahlivo pretypovať real na integer bez ohľadu na to, či je číslo kladné alebo záporné a dostať naozaj tú časť čísla, ktorá je pred desatinnou čiarkou. Vďaka.
Uff... Delphi 6..., to už bolo stráášne dávno...
No a neskúsil si napr. funkciu Round ? Odrezanie sa mi nepáči, niekedy je potrebné výsledok zaokrúhliť...
Príklad:
Round(12.75) = 13
Trunc(12.75) = 12
Int(12.75) = 12
i:=trunc(abs(y))*sign(y);
Ahojte,
pme: Niekedy naozaj zaokrúhliť treba ale niekedy práveže nie. Konkrétne v aplikácii, na ktorej pracujem, ide o prezeraniea vyhodnocovanie plochy, ktorá je rozdelená na menšie sektory, pričom hranice sektorov sú určené celými číslami, takže 12.75 spadá ešte do jedného ale 13 alebo 13.12 už do iného sektora, takže vyhodnotenie bude nesprávne. Problematika mojej aplikácie je trochu zložitejšia, preto tu nechcem rozpútavať debatu o tom, ako riešiť problém vyhodnocovania. Šlo mi čiste o tie prípady, kedy trunc(12,75) nevráti 12 ako je napríklad ten z môjho pôvodného príspevku. Kým trunc(-16275) vráti -16275 konštrukcia
var a:string;x,y:real;i:integer;
begin
a:='-16.275';
x:= strtofloat(ansireplacetext( a,'.',','));
y:=x*1000;
i:=trunc(y);
end;
vráti -16274 a podobný výsledok som spozoroval aj v niekolkých ďalších prípadoch, hoci v 98% iných výskytoch trunc sa toto nedeje. Je mi jasné čo píše MM o reálnych číslach, no práve preto som položil otázku, ako spolahlivo dosiahnuť správny výsledok. Round zaokrúhluje k najbližšiemu celému číslu a v helpe som nevidel možnosť vynútiť zaokrúhlenie iba smerom nadol či nahor. Zaujímavé je aj to, že v mojej vyššie uvedenej konštrukcii, kde mám pôvodne string s číslom '16275' a ten cez strtofloat prevediem na real je výsledkom premenná y, ktorá po výpise cez showmessage(floattostr(y)) vypíše 16275.000000 a po prevedení cez trunc už 16274. Naozaj neviem prečo to tak je, no zbadal som to aj pri niektorých iných číslach, hoci ide možno o mizivé percento no predsa.
wikan: zápis trunc(abs(y)*sign(y) vracia kvôli trunc ten istý chybný výsledok, pretože trunc vráti v tomto prípade nesprávnu hodnotu aj keď argumentom je to isté ale kladné číslo.
Si predsa ten string parsuj sam rovno na integer bez tej bodky. Si programator alebo lepic?
(a nepouzivaj ziadne floaty, ak chces mat presne cisla, resp. ak nevies/nechces uvazovat s chybami zapisu/vypoctu realnych cisel)
A trunc(12,75) vrati vzdy 12 :)
Problém s funkciou Trunc je ten, že Ty používaš ako parameter typ Real a nie Extended...
skúsil som Tvoju hodnotu -16,275 previesť Tvojim postupom cez funkciu Trunc a výsledok je naozaj -16274...
avšak ak použijem ako vstupný parameter hodnotu nie Real ale Extended - funkcia Trunc "zázračne" vráti hodnotu -16275
Cim si ale chybu neodstranil, len si zuzil mnozinu cisel pri ktorej nastane :) To vas fakt nikto neucil pracovat s floatmi? Sa uci snad v 2rocniku na vyske.
Tuna jasne vidno ze to je periodicke aj v double precision IEEE 754
http://www.binaryconvert.com/result_double.html?de cimal=049054046050055053
(perioda je 0011)
Skutocna hodnota je 16.2749999999999985789145284798
Je divne ze ten trunc ti vrati 16.275, asi sa tvorca trunc zlutoval a urobil tam zaokruhlovanie na 10cifre za bodkou apod
Ak to stale nechapes tak si to zadaj sem
http://www.h-schmidt.net/FloatConverter/IEEE754.ht ml
to je float presnost a vidis ze float ma chybu uz na 7.cifre. Doble ju bude mat mensiu ale bude tam furt, ptz vacsina neperiodickych desatinnych cisel v desiatkovej sustave sa neda v dvojkovej sustave zapisat presne (neperiodicky, bolo by to v dvojkovej sustave periodicke cislo).
No v Delphi existujú viaceré dátové typy s desatinnými miestami:
Typ Real v podstate odpovedá typu Double...
to je fuk ked to je periodicke tak to nebude presne nikdy.
Áno presné to nebude nikdy...
Tu Ti dávam ako je implementovaná funkcia priamo z jednotky System (Delphi)
Pekne, ale to sa mi dekodovat fakt nechce
(nepoznam presne zhlavy instrukcie FP koprocesora)
No a tom to je... Presnosť v pohyblivej desatinnej čiarke záleží od inštrukcií procesora...
JEZISIKRISTE
Urob si tam namiesto toho *1000 napriklad *100000000000000 a kukaj na ten vysledok :)
Pochopiteľne ak použijem typ Integer...
ak použijem Int64= výsledok je správny -> (rozsah...)
Chyba tam bude vzdy, pridaj nuly
Nechce sa mi to teraz vsetko konvertovat na binarku (ci to je fakt periodicke a studovat specifikacie IEEE), proste sa to takto nerobi. Potom to spustis na nejakom AMD alebo inom CPU a budes tam mat 274? Ked chce clovek presnost tak nech si ten string odparsuje rovno do integeru, to je snad kod na max 10 riadkov
Súhlasím s Tebou - možnosti nie sú neobmedzené (p.s. môj CPU AMD Athlon-II).
A tiež súhlasím, že je najlepšie to parsovať rovno zo stringu (tu nechápem prečo zadávanie čísla s desatinnou čiarkou a následné násobenie 1000...).
Este je to druhe riesenie a to je to ze si definuje s akou chybou floatu rata a ktora bude mensia ako minimalne mozny rozdiel toho co zada uzivatel, t.j. ak urobi napr.
integer = trunc(abs(float)+0.0001) * sign(float)
tak tym tento problem vyriesi, ptz uzivatel stejne nikdy nezada 16,274999 a nebude pri tom mysliet 16,274 :) (ale nechapem ani ja ze co to tam ma za vstup a preco robi to co robi)
Ono sa to ale takto v real-life vstupoch od užívateľa nerobí...
Proste obmedzíš zadanie vstupu od užívateľa (nástrojov je v Delphi hneď niekoľko...) napr. na určitý počet desatinných miest,
alebo mu rovno predhodíš do editačného poľa definovanú masku vstupu, aby tam náhodou nezadal písmeno namiesto čísla atď...
v jeho prípade/príklade by som to urobil asi takto:
Ale potom by si musel zaistit ze uzivatel zada za desatinnu ciarku vzdy 3 cifry, (aj ,000) inac keby som zadal 15,0 tak by si namiesto 15000 mal len 150. Ak to vies jednoducho zaistit tak OK. Ak ne tak parsovat znaky v cykle (najdem ciarku a potom este 3x parsujem ak je koniec tak tam hadzem nuly, alebo ked som nenasiel ciarku a uz je koniec tak pridam 3x nulu)
Pripadne to s tym +0.0001 je tiez moznost, chyby realnych cisel si ma programator stejne prehodnotit v algoritme viem ze nieco ma chybu max 1E-15 apod a ze to nasobim (ten nasobitel ma tiez chybu takze uz mam chybu na druhu resp. este aj vynasobenu tym cislom) a mocnim a janeviemco tak tie operacie urobim aj s chybou, a viem potom definovat ze moj algoritmus ma na vystupe chybu maximalne tolko a tolko (zavisi od pouziteho datoveho typu a operacii samozrejme). Nasledne si mozem ak zaokruhlujem len nadol tu max. chybu k vysledku priratat apod.
(P.S. vypada to potom sice dost debilne to +0.0001, ale fungovat to bude :D)
Zaujímavá debata a zaujímavý problém. Preto som sa rád zapojil do debaty a páči sa mi Tvoj konštruktívny postoj k riešeniu veci.
Tak trochu mimo OT:
stále tvrdím, že programátor si má presne premyslieť čo ide "stvoriť", musí si správne navrhnúť algoritmus riešenia problému a ten následne aplikovať.
A vychádza mi po tých rokoch, že napísať program zaberie cca 30-40% času, zvyšok je testovanie, ladenie a hľadanie chýb...
Ja som tieto pocty/hodnotenie celkovej chyby vypoctu robil len raz, na vyske ked nam to prednasali, a nemal som to rad, keby som mal robit program na vypocet drahy rakety, tak by ma asi jeblo, tam si to asi musi clovek sakra 5x prehodnotit aby nepristal kdesi kilometer vedla mesiaca vo vakuu
Zaokruhluje to priamo FP koprocesor (CPU), bity nastavuju v controlworde
(neviem preco ten extended CPU hodi na 275 ked v skutocnosti mal urobit 274 co by bolo SPRAVNE)
http://www.efg2.com/Lab/Library/Delphi/MathFunctio ns/FPUControlWord.Txt
na F00 t.j. bit8,9 = 11 a bit10,11 = 11
A necha CPU nech to zaokruhli.
T.j. mas vadne CPU, ptz to mal zaokruhlit nadol, neni to ten pentium 1 ci ktory?
Nikoliv trunc(abs(y)*sign(y)) ale trunc(abs(y))*sign(y).
Pozice závorek tam není jen tak pro srandu.
pme: vďaka, chyba bola skutočne v argumente typu real, pričom mal byť extended. Teraz to už naozaj pracuje ako má. Ja som bol vždy vedený k tomu, aby som používal úsporné typy teda napr aj word alebo bite keď je zbytočné použiť integer a tak som aj v mojom projekte použil real namiesto extended, lebo som mal za to, že real je vzhľadom na počet použitých bitov úspornejší a ja sa hodnotami určite pohybujem v rozsahu -180000.000 až 180000.000 s počtom desatinných miest max 3, aj keď samozrejme chápem že sú obsadené aj ďalšie miesta za desatinnou čiarkou, ale pre prácu ich nepotrebujem a ignorujem.
MM: S tou raketou vystrelenou na mesiac a pristátim kilometer vedla si v podstate trafil kliniec po hlavičke, lebo aplikácia, na ktorej pracujem, prezerá mapu, takže tie čísla sú vlastne gps súradnice. Mám negraficky lineárne spracovanú databázu gps pozícií takmer celého sveta, pričom rozlíšenie je relatívne velké - až 3 desatinné miesta. Nemám ale jednotlivé body, ale plochy, určené lavým dolným a pravým horným rohom, takže napr 48.120,17.060 až 48.159,17.099 je Bratislava - Staré mesto. Pohybom šípky sa v podstate zvyšuje a znižuje latitúda a longitúda, čo by sa za normálnych okolností malo diať tak, že súradnice, zapísané v premenných typu real alebo extended by mali zvyšovať a znižovať hodnotu a teda by som k nim mal pripočítavať a odpočítavať niekde na úrovni tretieho miesta za desatinnou čiarkou. To ale v praxi práve spôsobuje tú výchylku, o ktorej si písal, po pár desiatkach či stovkách operácií si už niekde úplne inde, preto to robím tak, že súradnice sú vynásobené 1000 a samotný pohyb je rátaný tesne za desatinnou čiarkou. Tak sa posunieš napr zo 48120.000000 na 48159.7872, čo teraz treba vyhodnotiť a oznámiť, čo sa na danej súradnici nachádza. Keďže to za čiarkou je len pomocná hodnota pre rátanie pohybu a rozlíšenie mapy mám len na 3 desatinné miesta gps, to za čiarkou dám preč a výsledné 48159 zodpovedá latitúde 48.159 (rovnako tak aj s longitúdou). A práve tu je zaokrúhlenie kontraproduktívne, lebo kým 48.159 je ešte Bratislava staré mesto, 48.160 je už niečo iné. Preto som sa potreboval vyhnúť zaokrúhleniu a ak trunc vracalo chybný výsledok, tak som sa geograficky naozaj ocitol o kilometer inde.
Ešte raz vďaka všetkým.
Ani extended neni spravne. (mozu tam vzdy vznikat chyby pri tom zaokruhlovani, zavisi to od zadaneho cisla)
BTW. vcera som aj hladal ze ci nahodou pri extended sa neuklada binarne inac, a ne, aspon podla toho co som nasiel specifikacie. Takze aj tam ti vznikyju chyby zapisu, neviem zhlavy preco to konkretne cislo zaokruhli u extended na to vyssie cislo (nechce sa mi to prevadzat rucne ani instalovat nejake delphi kvoli tomu :) ale moze to suvisiet len s tym konkretnym cislom a ine moze byt zas blbo.
Pri praci s plavajucou ciarkou musis musis vzdy mysliet aj na chybu zapisu a vypoctu, a pri konverziach sa to musi vzdy zaokruhlovat aspon s prihliadnutim na maximalnu moznu odchylku po tom vypocte, t.j. priratat si tam aspon tu ocakavanu max. chybu, apod.
Napr. tuto mas viac pokecu, ak by ta to nahodou zaujimalo. Vidiet tam jasne ze aj extended ma chyby, samozrejme.
articles-floats.html
Napr. zaujimave je aj toto
To by vysvetlovalo ze preco nahodou toto cislo v extended nahodou zaokruhluje tak, ale bude to zavisiet od zadaneho cisla (skusaj si rozne cisla alebo si urob program aby ti preskusal vsetky mozne cisla a vypisal ked to pri niektorom bude nespravne, a napis jak si dopadol :)