Přidat otázku mezi oblíbenéZasílat nové odpovědi e-mailemVyřešeno 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.

Jsou zobrazeny jen nové odpovědi. Zobrazit všechny
Předmět Autor Datum
Ahojte, pme: Niekedy naozaj zaokrúhliť treba ale niekedy práveže nie. Konkrétne v aplikácii, na ktor…
Stefan77 11.02.2016 20:34
Stefan77
A trunc(12,75) vrati vzdy 12 :) nový
MM.. 11.02.2016 20:45
MM..
Problém s funkciou Trunc je ten, že Ty používaš ako parameter typ Real a nie Extended... function T… nový
pme 11.02.2016 20:49
pme
Cim si ale chybu neodstranil, len si zuzil mnozinu cisel pri ktorej nastane :) To vas fakt nikto neu… nový
MM.. 11.02.2016 20:53
MM..
Tuna jasne vidno ze to je periodicke aj v double precision IEEE 754 http://www.binaryconvert.com/res… nový
MM.. 11.02.2016 21:01
MM..
Ak to stale nechapes tak si to zadaj sem http://www.h-schmidt.net/FloatConverter/IEEE754.ht ml to je… nový
MM.. 11.02.2016 20:52
MM..
No v Delphi existujú viaceré dátové typy s desatinnými miestami: Dec1 : Single; // 7 significant di… nový
pme 11.02.2016 21:02
pme
to je fuk ked to je periodicke tak to nebude presne nikdy. nový
MM.. 11.02.2016 21:03
MM..
Áno presné to nebude nikdy... Tu Ti dávam ako je implementovaná funkcia priamo z jednotky System (D… nový
pme 11.02.2016 21:12
pme
Pekne, ale to sa mi dekodovat fakt nechce :-) (nepoznam presne zhlavy instrukcie FP koprocesora) nový
MM.. 11.02.2016 21:18
MM..
No a tom to je... Presnosť v pohyblivej desatinnej čiarke záleží od inštrukcií procesora... nový
pme 11.02.2016 21:21
pme
JEZISIKRISTE Urob si tam namiesto toho *1000 napriklad *100000000000000 a kukaj na ten vysledok :) nový
MM.. 11.02.2016 21:25
MM..
Pochopiteľne ak použijem typ Integer... Int8 : Integer; // -2,147,483,648 to 2,147,483,647 Int9 : I… nový
pme 11.02.2016 21:34
pme
Chyba tam bude vzdy, pridaj nuly :-) Nechce sa mi to teraz vsetko konvertovat na binarku (ci to je f… nový
MM.. 11.02.2016 21:39
MM..
Súhlasím s Tebou - možnosti nie sú neobmedzené (p.s. môj CPU AMD Athlon-II). :-) A tiež súhlasím, ž… nový
pme 11.02.2016 21:43
pme
Este je to druhe riesenie a to je to ze si definuje s akou chybou floatu rata a ktora bude mensia ak… nový
MM.. 11.02.2016 21:56
MM..
Ono sa to ale takto v real-life vstupoch od užívateľa nerobí... Proste obmedzíš zadanie vstupu od už… nový
pme 11.02.2016 22:07
pme
Ale potom by si musel zaistit ze uzivatel zada za desatinnu ciarku vzdy 3 cifry, (aj ,000) inac keby… nový
MM.. 11.02.2016 22:16
MM..
Zaujímavá debata a zaujímavý problém. Preto som sa rád zapojil do debaty a páči sa mi Tvoj konštrukt… nový
pme 11.02.2016 22:24
pme
Ja som tieto pocty/hodnotenie celkovej chyby vypoctu robil len raz, na vyske ked nam to prednasali,… nový
MM.. 11.02.2016 22:40
MM..
Zaokruhluje to priamo FP koprocesor (CPU), bity nastavuju v controlworde http://www.efg2.com/Lab/Lib… nový
MM.. 11.02.2016 21:24
MM..
Nikoliv trunc(abs(y)*sign(y)) ale trunc(abs(y))*sign(y). Pozice závorek tam není jen tak pro srandu. nový
Wikan 11.02.2016 20:54
Wikan
pme: vďaka, chyba bola skutočne v argumente typu real, pričom mal byť extended. Teraz to už naozaj p… nový
Stefan77 12.02.2016 19:00
Stefan77
Ani extended neni spravne. (mozu tam vzdy vznikat chyby pri tom zaokruhlovani, zavisi to od zadaneho… nový
MM.. 12.02.2016 19:29
MM..
BTW. vcera som aj hladal ze ci nahodou pri extended sa neuklada binarne inac, a ne, aspon podla toho… nový
MM.. 12.02.2016 19:42
MM..
Napr. tuto mas viac pokecu, ak by ta to nahodou zaujimalo. Vidiet tam jasne ze aj extended ma chyby,… poslední
MM.. 12.02.2016 20:00
MM..

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.

Problém s funkciou Trunc je ten, že Ty používaš ako parameter typ Real a nie Extended...

function Trunc ( const Number : Extended ) : Integer;

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

var
  X: Extended;
  Y: Integer;
begin
  X := StrToFloat(Edit1.Text);
  Y := Trunc(X*1000);
  Label1.Caption :=  IntToStr(Y);
end;

No v Delphi existujú viaceré dátové typy s desatinnými miestami:

Dec1 : Single; // 7 significant digits, exponent -38 to +38
Dec2 : Currency; // 50+ significant digits, fixed 4 decimal places
Dec3 : Double; // 15 significant digits, exponent -308 to +308
Dec4 : Extended; // 19 significant digits, exponent -4932 to +4932

Typ Real v podstate odpovedá typu Double...

Áno presné to nebude nikdy...

Tu Ti dávam ako je implementovaná funkcia priamo z jednotky System (Delphi)

{$IF     defined(CPUX64)}
function _Trunc(Val: Extended): Int64;
asm
        .NOFRAME
        CVTTSD2SI RAX, XMM0
end;
{$ELSEIF defined(CPUX86)}
procedure _TRUNC;
asm
        { ->    FST(0)  Extended argument       }
        { <-    EDX:EAX Result                  }

        SUB     ESP,12
{$IFDEF IOS} // iOS/Simulator
        CALL    FClearExcept
{$ENDIF IOS}
        FNSTCW  [ESP].Word          // save
        FNSTCW  [ESP+2].Word        // scratch
        FWAIT
        OR      [ESP+2].Word, $0F00  // trunc toward zero, full precision
        FLDCW   [ESP+2].Word
        FISTP   qword ptr [ESP+4]
        FWAIT
        FLDCW   [ESP].Word
{$IFDEF IOS} // iOS/Simulator
        CALL    FCheckExcept
{$ENDIF IOS}
        POP     ECX
        POP     EAX
        POP     EDX
end;

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:

var
  I: Integer;
  S: String;
begin
  S := StringReplace(Edit1.Text, ',', '', [rfReplaceAll, rfIgnoreCase]);   <- odstránim znak desatinnej čiarky zo zadaného vstupu
  I := StrToInt(S);  <- prevediem text na číslo
  Label1.Caption :=  IntToStr(I);  <- tento spätný prevod na string je už len kvôli kontrole...
end;

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. :beer:

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 :-D

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.

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

The x87 Floating Point Unit

The x87 FPU knows 4 rounding modes (see the FPU control word section of this article). So how does the FPU round? Say an operation on a Single produced an intermediate result that has some extra low bits. The extended mantissa looks like this:

1.0001 1100 0100 1100 1001 0111

The underlined bit is the bit to be rounded. There are two possible values this can be rounded to, the value directly below and the value directly above:

1.0001 1100 0100 1100 1001 011
1.0001 1100 0100 1100 1001 100

Now what happens depends on the rounding mode. If the rounding mode is the default — round to nearest “even” — it will get rounded to the value that has a 0 as least significant bit. You can probably guess which of the two values is chosen for the other rounding modes.

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 :)

Zpět do poradny Odpovědět na původní otázku Nahoru