Close
Faqja 0 prej 2 FillimFillim 12 FunditFundit
Duke shfaqur rezultatin -9 deri 0 prej 15
  1. #1
    i/e regjistruar Maska e Borix
    Anëtarësuar
    17-01-2003
    Postime
    2,316

    Post Rreth aritmetikës së numrave dhjetorë në C/C++

    Gjate programimit ne C te nje algoritmi gjenetik per nje probleme optimizimi lineare, hasja probleme me operacionet aritmetike te numrave dhjetore, te tipit double (ose float). Teksa nga ana teorike gjithcka prisja te dilte sipas projektimit, ne anen praktike (dhe kjo eshte normale deri diku), hasja probleme teknike, lidhur me specifikimet teknike te ANSI C, sipas IEEE. Kur te gjithe kodin ne C e perktheva ne VB.NET, nuk hasa me asnje problem (madje, edhe shpejtesia e ekzekutimit u rrit - gje qe eshte e pritshme ne programimin e orientuar me objekte). Per te mos humbur thelbin e kesaj teme, vendosa te kerkoj ne internet dhe hasa ne faqen e meposhtme, tek kodet e se ciles dua te ndalem pak: http://www.macaulay.ac.uk/fearlus/fl...int/index.html.

    Ne faqe, tipi double percaktohet thjesht dhe bukur si me poshte:

    [ident]Aritmetika me numrat dhjetore eshte nje menyre standarde e te punuarit me numra jo te plote ne nje mashine dixhitale. Tipi eshte projektuar per te ngjallur iluzionin se po punohet me numra dhjetore, teksa ne fakt nje mashine punon rigorozisht me nje bashkesi te fundme numrash (teksa dihet qe numrat dhjetore jane te pafundem).[/ident]

    Ne gjuhen C/C++, per te punuar me ndryshore te tipit reale, duhet t'i deklarojme si double (ose float, por double ka me shume saktesi). Marrim shembullin qe eshte dhene ne faqen, url-ne e se ciles kam paraqitur me lart:

    Kodi:
    double ENERGY = 1.2;
    ENERGY = ENERGY - 0.4;
    ENERGY = ENERGY - 0.4;
    ENERGY = ENERGY - 0.4;
    Variabli ENERGY deklarohet si double dhe i jepet nje vlere fillestare prej 1.2. Paskesaj, hiqet 3 here vlera 0.4. Matematikisht, rezultati ne fund duhet te jete zero. Megjithate, nje ekzekutim i thjeshte i kodit te mesiperm nxjerr kete rezultat:

    Kodi:
    -0.000000
    Kjo mungese saktesie e specifikimit te tipit double sjell gabime ne rrumbullakosje dhe perafrime te tjera, te cilat nga kendveshtrimi natyror (ose aritmetik) jane te sakta, ndersa nga kendveshtrimi i mashines (teknik) jane te ndryshme. Teksa per ne (sipas matematikes) vlera -0.0 eshte e njejte me vlere 0, per mashinen keto dy vlera jane te ndryshme. Shpjegimi, sipas faqes, qendron tek bijeksioni midis bashkesise se numrave reale dhe struktures teknike (te mashines) te percaktuar nga IEEE.

    Per shembull, nese me lart do te krahasonim vleren e variablit ENERGY me vleren ZERO, rezultati nuk do te ishte i njejte:

    Kodi:
    double ZERO = 0.0;
    ENERGY = 1.2;
    ENERGY = ENERGY - 0.4;
    ENERGY = ENERGY - 0.4;
    ENERGY = ENERGY - 0.4;
    
    if (ENERGY==ZERO)
       puts("ENERGY = ZERO");
    else
       puts("ENERGY <> ZERO");
    Ne kete rast, vlera e ENERGY eshte -0.0, dhe, sipas mashines, kjo vlere eshte me e vogel se vlera zero. Ne fakt, aritmetikisht keto jane te barabarta. Por, nese perdorim funksionin fabs() per te marre vleren absolute te ENERGY, atehere rezultati del zero, e serish nuk njihet i njejte me vleren ZERO nga mashina. Dhe, nese perserisim edhe njehere krahasimin e mesiperm, vlera e ENERGY (qe tani eshte 0.00000) del me e madhe se vlera e ZERO, qe eshte deklaruar qe ne fillim si 0.0.

    Ky eshte nje problem qe mund te shkaktoje konfuzion tek programuesi dhe duhet te kene kujdes ata qe programojne ne C.

    Nder probleme te tjera qe paraqiten ne faqe, eshte edhe vecoria e shoqerimit aritmetik, e cila duket sikur funksionon per nje set, por jo per nje tjeter. Per shembull, nese krahasohet vlera 0.1+0.2+0.3 me vleren 0.6, rezultati eshte i njejte vetem ne rastin kur kllapat vendosen si me poshte:

    [code]
    if (0.1 + (0.2 + 0.3)) == 0.6); // rezultati del ekuivalent
    [code]

    Ne raste te tjera, si psh. if (0.1 +0.2 + 0.3 == 0.6); if nuk ekzekutohet sepse mashina, per shkak te mungeses se saktesise (precisionit) si pasoje e paraqitjes teknike te numrave dhjetore, nuk arrin t'i "shohe" si te njejte shumen e 0.1 +0.2 +0.3 dhe vleren 0.6.

    Kjo therret per nje kujdes te larte gjate programimit. Ne algoritmin gjenetik qe permenda me lart, u detyrova te marr nje variabel (RESULT) dhe te mbledh dy nga dy te gjithe operandet qe ishin me shume se dy. Ne kete menyre, mbahet ekuivalenca logjike (aritmetike) dhe "genjehet" edhe mashina. Me poshte do te paraqes testet perkatese ne .NET.
    "The rule is perfect: in all matters of opinion our adversaries are insane." (M. Twain)

  2. #2
    i/e regjistruar Maska e Borix
    Anëtarësuar
    17-01-2003
    Postime
    2,316
    Me poshte po paraqes rezultatet ne VB.NET Console App. Sic del nga ekzekutimi, gjithcka eshte ekuivalente dhe gabimet e specifikimeve teknike qe dalin ne C/C++ nuk ndodhin ne .NET, sipas ketij eksperimenti te vogel.

    Cdo sugjerim ne lidhje me problemin e mesiperm ne C/C++ eshte i mirepritur per diskutim.

    Kodi:
    Module FloatingArithmetic
    
        Sub Main()
    
            TestENERGY()
            TestAssociativeProperty()
    
            Console.ReadLine()
    
        End Sub
    
        Private Sub TestENERGY()
            Dim ENERGY As Decimal = 1.2, ZERO As Decimal = 0.0
            ENERGY -= 0.4
            ENERGY -= 0.4
            ENERGY -= 0.4
    
            If ENERGY = ZERO Then
                Console.WriteLine("ENERGY == ZERO")
            Else
                Console.WriteLine("ENERGY <> ZERO")
            End If
        End Sub
    
        Private Sub TestAssociativeProperty()
    
            Dim ONE, TWO, THREE, SUM As Decimal
    
            ONE = 0.1
            TWO = 0.2
            THREE = 0.3
            SUM = 0.6
    
            If (ONE + TWO + THREE = SUM) Then
                Console.WriteLine("0.1 + 0.2 + 0.3 == 0.6")
            Else
                Console.WriteLine("0.1 + 0.2 + 0.3 <> 0.6")
            End If
    
            If ((ONE + TWO) + THREE = SUM) Then
                Console.WriteLine("(0.1 + 0.2) + 0.3 == 0.6")
            Else
                Console.WriteLine("(0.1 + 0.2) + 0.3 <> 0.6")
            End If
    
            If (ONE + (TWO + THREE) = SUM) Then
                Console.WriteLine("0.1 + (0.2 + 0.3) == 0.6")
            Else
                Console.WriteLine("0.1 + (0.2 + 0.3) <> 0.6")
            End If
        End Sub
    
    End Module
    "The rule is perfect: in all matters of opinion our adversaries are insane." (M. Twain)

  3. #3
    i/e regjistruar
    Anëtarësuar
    16-11-2005
    Postime
    8,691
    Mbase nuk me ka rene rasti ti perdor shume vlerat double ne C po nuk kam hasur probleme te tilla.
    Ndoshta eshte nje problem i kompilatorit?

    Personalisht kam perdorur gcc ne linux dhe Dev-C++ per windows(ky eshte i mire edhe per te migruar programet nga linux ne windows dhe anasjelltas).

  4. #4
    i/e regjistruar Maska e Borix
    Anëtarësuar
    17-01-2003
    Postime
    2,316
    Varet se si i trajton kompiluesi numrat dhjetore. Por ne thelb, gjithcka varet nga inkorporimi i specifikimit teknik IEEE 754-1985. Meqenese ne kete protokoll nje numer perfaqesohet nga shenja (0 per pozitive dhe 1 per negative), nga fuqia, dhe nga pjesa dhjetore, atehere eshte logjike te kemi edhe nje vlere minus zero (-0), ku bitet tek fuqia dhe pjesa dhjetore jane te gjitha zero, por vlera e bitit te shenjes eshte nje. Per natyren kjo eshte e barazvlefshme me vleren 0, por per mashinen qe bazohet tek IEEE 754, kjo eshte e ndryshme.

    Kompilatore moderne si .NET, me sa duket, kane shtuar kode ne kompilueset e tyre per te njehsuar dhe eliminuar "gabime" te tilla teknike. Per shembull, per te detektuar nese rezultati eshte -0 ose ekzaktesisht 0 (si ne rastin e ENERGY me lart, ku rezultati ishte minus zero), perdoret limiti ne infinit. Pra, limiti i 1/ENERGY teksa ENERGY shkon drejt zeros nga e majta (nga ana negative) eshte minus infinit. Ne terma kompjuterike (te mashines), nje vlere si (1/ENERGY) = 1/(-0) eshte me e vogel se zero, sepse rezultati eshte (teorikisht) -infinit. Ne rastin kur ENERGY eshte +0 (dicka shume pak me e madhe se zero), atehere rezultati 1/(+0) eshte +infinit. Pra, me ane te nje funksioni TestIfZero(), mund te testojme nese nje vlere e dhene eshte ekzaktesisht e barabarte me zero ose jo.

    Menyra interesante per te marre parasysh gabimet relative qe ndodhin midis vleres qe ne presim dhe vleres qe mashina nxjerr ne realitet jepet nga Dawson ne faqen http://www.cygnus-software.com/paper...ringfloats.htm. Ketu ka detaje teper interesante.

    Tek kjo faqe ka nje IEEE 754 converter: http://www.h-schmidt.net/FloatApplet/IEEE754.html - Jepni vleren -0.0 dhe pastaj vleren 0.0 dhe beni krahasimet e tresheve perkatese {shenje, fuqi, thyese}. Tek e para, shenja do te jete 1, ndersa tek e dyta 0. Per kete arsye, mashina dixhitale e printon te paren si -0, ndersa te dyten si 0 dhe per mashinen keto jane te ndryshme.
    Ndryshuar për herë të fundit nga Borix : 24-06-2008 më 07:26
    "The rule is perfect: in all matters of opinion our adversaries are insane." (M. Twain)

  5. #5
    i/e regjistruar
    Anëtarësuar
    19-07-2007
    Postime
    90
    Ne vend të:
    if (x==y) /* Ky krahasim nuk funksionon gjithmonë për shkak te saktësisë se kufizuar*/

    duhet te përdoret:

    if (fabs(x-y) < FLT_EPSILON)

    funksionon gjithmonë.

  6. #6
    i/e regjistruar Maska e Borix
    Anëtarësuar
    17-01-2003
    Postime
    2,316
    funksionon gjithmonë.
    Mbase ka funksionuar gjithmone ne te gjitha rastet qe keni hasur ju. Artikulli me url-ne qe kam dhene me lart, merr parasysh edhe disa kazuse te tjera...
    "The rule is perfect: in all matters of opinion our adversaries are insane." (M. Twain)

  7. #7
    Programues Softueresh Maska e edspace
    Anëtarësuar
    04-04-2002
    Vendndodhja
    Filadelfia, SHBA
    Postime
    2,565
    Në rrethin e shkencëtarëve dhe akademikëve është tepër i njohur dhe me kompetenca të mëdha artikulli (libri)
    Atë që çdo shkencëtar kompjuteri duhet të dijë për aritmetikën me numrat dhjetorë (What Every Computer Scientist Should Know About Floating-Point Arithmetic). Ky artikull shpjegon në hollësi problemet që kompjuterat kanë me prezantimin e numrave dhjetorë dhe aritmetikën me ta. Është 87+ faqe, por mund të lexoni ato pjesë që u interesojnë.
    Edi

  8. #8
    i/e regjistruar
    Anëtarësuar
    12-06-2006
    Vendndodhja
    Redmond, WA, USA
    Postime
    181
    Ky është problem që një pjesë e anëtarëve të komitetit të C++ kërkojnë që përdorimi i operatorit "==" në ndryshore të tipit double precision (pra ndryshore të tipit jo egzakt por me saktësi më të madhe) dhe të tipit float të gjenerojë gabime.
    Sipas tyre, tipi i ndryshoreve jo-egzakte nuk duhet të krahasohet me vlerë egzakte siq i krahasojmë dy intexher.
    Tash-për-tash në çdo krahasim të vlerës jo-egzakte duhet ti shtohet (+-) edhe margjina e tolerancës.


    Nuk jam i sigurt, por në C++ mendoj se ndryshoret e tipit double janë 80-bitëshe. Dhe është shumë lehtë që vlerën 0.4 compajleri ta shëndërrojë në 0.39999999999999999999999999999.
    Vërtetoje me printf dhe shfaq sa më shumë numra pas pikës dhjetore sepse numrat double edhe rrumbullaksohen për prezentim më të thjeshtë.

    Prandaj kur ti bënë:
    double ENERGY = 1.2;
    ENERGY = ENERGY - 0.4;
    ENERGY = ENERGY - 0.4;
    ENERGY = ENERGY - 0.4;

    duket si:
    double ENERGY = 1.2;
    ENERGY = ENERGY - 0.39999999999999999999999999999;
    ENERGY = ENERGY - 0.39999999999999999999999999999;
    ENERGY = ENERGY - 0.39999999999999999999999999999;

    e kjo nuk është zero.

  9. #9
    i/e regjistruar Maska e Borix
    Anëtarësuar
    17-01-2003
    Postime
    2,316
    Po, ajo eshte e sakte Agron, sepse bazuar ne bijeksionin matematikor qe IEEE i ka bere boshtit te numrave reale me bashkesine e ketyre numrave ne mashinat diskrete, saktesia humbet per numra si 0.1, 0.2, 0.4, etj... Pra, bijeksioni eshte bere me intervale. Per shembull, vlera 0.2 eshte paraqitur si nje interval midis 0.19999999999 dhe 0.20000000001, dhe kjo logjike eshte aplikuar edhe per numrat dhjetore me te vegjel se aq...
    "The rule is perfect: in all matters of opinion our adversaries are insane." (M. Twain)

  10. #10
    Programues Softueresh Maska e edspace
    Anëtarësuar
    04-04-2002
    Vendndodhja
    Filadelfia, SHBA
    Postime
    2,565
    Citim Postuar më parë nga Agron_ca Lexo Postimin
    Nuk jam i sigurt, por në C++ mendoj se ndryshoret e tipit double janë 80-bitëshe.
    Standardi i C++ nuk përcakton përmasat e tipeve primitive. Përmasat mund të ndryshojnë nga një përpilues në tjetrin në varësi të sistemit ku përpiluesi është instaluar. Për të gjetur përmasat, përdoret operatori sizeof. p.sh: sizeof(int)
    Edi

Faqja 0 prej 2 FillimFillim 12 FunditFundit

Tema të Ngjashme

  1. Sfidë: Mbledhja e numrave romak
    Nga edspace në forumin Arti i programimit
    Përgjigje: 0
    Postimi i Fundit: 16-06-2006, 17:55
  2. Përgjigje: 5
    Postimi i Fundit: 18-11-2005, 10:08
  3. Sfida nga Pr-Tech: Mbledhja e dy numrave 256 shifror
    Nga edspace në forumin Arti i programimit
    Përgjigje: 15
    Postimi i Fundit: 18-03-2005, 20:32
  4. Qeveria merr vendimin per shtimin e numrave ushtarake ne Irak
    Nga Hyllien në forumin Tema e shtypit të ditës
    Përgjigje: 1
    Postimi i Fundit: 25-02-2005, 16:56

Regullat e Postimit

  • Ju nuk mund të hapni tema të reja.
  • Ju nuk mund të postoni në tema.
  • Ju nuk mund të bashkëngjitni skedarë.
  • Ju nuk mund të ndryshoni postimet tuaja.
  •