« 1 2 »
Autor Zpráva
SeparateSK
Profil
Dobrý deň, robím kalkulačku na spočítavanie hodnôt atď, lenže nastal problém pri sčitovaní desatinných čísel, teda:
document.write(15.8+156.8);
Vypíše: 172.60000000000002, pritom výsledok je naozaj 172.6.
Ako sa to dá napraviť, aby bol výsledok presný?
Str4wberry
Profil
Časté potíže, zajímavosti a poučné debaty » Výsledkem výpočtu 1 - 0.9 není přesně 0.1
SeparateSK
Profil
Má to znamenať, že sa to nedá?
V iných prog. jazykoch to ide.


Vyriešené (našiel som na nete):
document.write(Math.round((15.8+156.8)*10)/10);
Joker
Profil
SeparateSK:
V iných prog. jazykoch to ide.
V jiných programovacích jazycích to vesměs funguje úplně stejně.

Vyriešené (našiel som na nete)
Tohle řešení ovšem ten problém nemusí úplně odstranit.

Je pravda, že ta kapitolka v FAQ je snad až příliš stručná. Jelikož v JS FAQ jsem se snad ještě nehrabal, napíšu sem, co by se tam mohlo doplnit:
Toto chování typicky působí problémy ve třech oblastech:
• Vypisování čísel uživateli, kdy se zobrazují čísla se spoustou desetinných míst (např. 0.09999999999999998 místo 0.1)
Řešení: Výsledky výpočtů v plovoucí čárce před zobrazením zaokrouhlujte na tolik desetinných míst, kolik chcete zobrazovat.

• Porovnávání nefunguje spolehlivě, například výsledkem ((1 - 0.9) == 0.1) je false. Pokud něco takového dáte jako podmínku cyklu, může vzniknout nekonečný cyklus.
Řešení: Snažte se v plovoucí čárce neporovnávat proti konkrétní hodnotě a raději používat operátory větší/menší. Např. když snižujete hodnotu reálného čísla a chcete vědět kdy dosáhne nuly, místo porovnání proti nule (promenna == 0) je bezpečnější podmínku zapsat jako menší než nějaké v daném kontextu dostatečně malé číslo (promenna < 0.0001, hodnotu je nutné zvolit podle situace)

• Násobení není asociativní operace, například výsledek (0.4 * 0.1 * 10) není stejný jako (0.4 * 10 * 0.1). To je zvlášť nepříjemné, pokud je v některém kroku výpočtu výsledkem velmi nízké číslo, které se pak násobí vysokým číslem (čímž se znásobí i ta nepřesnost).
Řešení: U výrazů počítajících s reálnými čísly preferujte zápis, který v jednotlivých krocích výpočtu pokud možno nedává extrémně vysoké nebo extrémně nízké mezivýsledky (například když chcete vynásobit čtyři čísla a, b, c, d a víte, že a budou řádově tisíciny, b setiny, c desítky a d miliony, lepší postup než a * b * c * d, kde první mezivýsledek jsou statisíciny a pak se násobí vysokými čísly, bude a * d * b * c, kde řády mezivýsledků jsou mezi desítkami a tisíci).
petr 6
Profil
A samozřejmě, pokud to jde, počítat s celými čísly místo s desetinnými. Například pokud počítáte peněžní částky, tak je počítat v halířích místo v korunách, v centech místo v eurech apod.
SeparateSK
Profil
Ďakujem, skúsil som to nejak spraviť, aby to prepočítavalo desatinné čísla na celé, nuž neviem či to nemá chyby/bugy, ale tu je výsledok:
    function makeop(a,b,op){
        var s=(a+'').split(".");
        var u=(b+'').split(".");
        var f=0;
        if(u.length==2||s.length==2)
            if(u.length==2&&s.length==2)
                f=(u[1].length>s[1].length)?u[1].length:s[1].length;
            else if(u.length!=2)
                f=s[1].length;
            else if(s.length!=2)
                f=u[1].length;
        var exp=(Math.pow(10,f));
        var res=0;
        if(op=='+')
            res=(((a*exp)+(b*exp))/exp);
        else if(op=='-')
            res=(((a*exp)-(b*exp))/exp);
        else if(op=='*')
            res=(((a*exp)*(b*exp))/Math.pow(exp,2));
        else if(op=='/')
            res=(((a*exp)/(b*exp)));
        return res;
    }
    var n1=1,n2=0.9,op="-";
    alert("#1: "+makeop(n1,n2,op) + " - " + (n1-n2));
    n1=15.8;
    n2=156.8;
    op="+";
    alert("#2: "+makeop(n1,n2,op) + " - " + (n1+n2));
_es
Profil
SeparateSK [#6]:
A čo ako tá funkcia robí? Lebo to vyzerá na prvý pohľad nezmyselne.
SeparateSK
Profil
Sčituje, odčituje, násobí, delí
makeop(cislo1,cislo2,operator); //platne operatory: "+","-","*","/"
_es
Profil
SeparateSK:
No ale to robia aj + - * a /. K čomu mi bude tvoja funkcia?
SeparateSK
Profil
K tomu,aby 1-0.9 dalo správny výsledok a nie 0.099999999998
_es
Profil
SeparateSK:
Postup k správnemu výsledku máš v [#4]. Rôzne pochybné funkcie na to nepotrebuješ.
SeparateSK
Profil
Ale zaokrúhlené číslo nie je vždy presné.
_es
Profil
SeparateSK:
A čo tá tvoja funkcia vo výsledku robí, okrem toho, že nejako zaokrúhľuje a to ešte dosť pochybne?
(1/3)*(2/3) (0.2222222222222222) mi pripadá presnejší výsledok, než cez tvoju funkciu makeop(1/3,2/3,"*") (0.22222222222222218).
SeparateSK
Profil
Áno máš pravdu moja funkcia nie je moc dobre navrhnutá na násobenie a delenie, význam tej funkcie je pôvodne previesť desatinné čísla na celé, sčítať ich alebo odčítať a potom ich previesť späť na desatinné, teda 1-0.9 => (1*10 - 0.9*10)/10 = (10 - 9)/10 = 1/10 = 0.1
Alphard
Profil
Dívám se na konstruktci
res=(((a*exp)+(b*exp))/exp);
Ani javascript to snad nepřetypuje na integer, vnitřní reprezentace zůstane stejná jako původní, tedy float (double). Takže to podle mě vůbec nic nezmění, nebo snad ano?

K dosažení vyšší přesnosti by bylo třeba předávat čísla jako stringy a vytvořit implementaci aritmetických funkcí, která s tím bude pracovat.
_es
Profil
Alphard:
Takže to podle mě vůbec nic nezmění, nebo snad ano?
Zmení, lebo vstupné číselné argumenty budú prevedené na text a z toho textu sú zase vytvárané iné čísla. No spôsob výpočtu je veľmi pofidérny.

SeparateSK:
význam tej funkcie je pôvodne previesť desatinné čísla na celé
Ako prevedieš podľa tvojho postupu na celé číslo (1/3)?

K dosažení vyšší přesnosti by bylo třeba...
Treba sa prispôsobiť tomu, čo tie čísla reprezentujú, čo je účel operácie, výsledku a pod. Neexistuje nejaká univerzálna „opravná“ funkcia.
SeparateSK
Profil
_es:
Ako prevedieš podľa tvojho postupu na celé číslo (1/3)?
Bohužiaľ nijak.

Treba sa prispôsobiť tomu, čo tie čísla reprezentujú, čo je účel operácie, výsledku a pod. Neexistuje nejaká univerzálna ‚opravná‘ funkcia.
Je to na spočítavanie peňazí - viem, že euro má sto centov ale napr. z T-Mobilu za hovor účtujú dokonca aj na stotiny centu.
Alphard
Profil
_es [#16]:
To zase přece ne, stringy jsou s a u. Z nich se určí počet desetinných míst a velikost koeficientu exp, samotný výpočet probíhá s původními a a b. Že je to velmi pofidérní souhlasím.

Treba sa prispôsobiť tomu, čo tie čísla reprezentujú
Neříkám, že ne. Práce s čísly jako stringy a funkcemi s nekonečnou přesností je podle mě matematicky možná, ale operand musí být výraz obsahující nevyčíslené zlomky a konstanty. To nás bohužel velmi rychle přivede na hranice výpočetních možností.

Celé tohle vlákno se mi zdá zbytečné, normální výsledek je třeba prostě zaokrouhlit. Když se dodrží zásady, které popsal Joker, nepřesnost se po zaokrouhlení neprojeví.
SeparateSK
Profil
Lenže problém je ten, že mám viacero hodnôt a tie sa postupne spracúvajú, teda
var arr1=new Array(15.8,172.6);
var arr2=new Array(156.8,0.01);
for(var i=0;i<arr1.length;i++){
    var v=Math.round((arr1[i]+arr2[i])*10)/10; //ako viem ci to mam zaokruhlit?
    document.write(v+"<br />");
}
15.8+156.8 a 172.6+0.01 dajú rovnaké výsledky.
Ako mám teda vedieť ktoré číslo zaokrúhliť a ktoré nie?
Joker
Profil
SeparateSK:
Ale zaokrúhlené číslo nie je vždy presné.
Ano, to je princip zaokrouhlování :-)

On ten skript potřebuje naprosto přesná čísla? To má u iracionálních čísel dost smůlu (jaká je přesná hodnota odmocniny ze dvou?)

Ako mám teda vedieť ktoré číslo zaokrúhliť a ktoré nie?
Zaokrouhlit všechno, na takovou přesnost, jaká je potřeba.
SeparateSK
Profil
Joker:
On ten skript potřebuje naprosto přesná čísla? To má u iracionálních čísel dost smůlu (jaká je přesná hodnota odmocniny ze dvou?)
Nemusia byť presné, ale predsa len pri finančníctve radšej nebudem robiť omyly.

Zaokrouhlit všechno, na takovou přesnost, jaká je potřeba.
Teda:
     var a=2; //pocet desatinnych miest cisla 1
     var b=3; //pocet desatinnych miest cisla 2
     var zaokruhlit_na=a>b?a:b; //3 v tomto pripade (lebo 0.01+0.001 = 0.011)
pri sčitovaní.
_es
Profil
Alphard:
Z nich se určí počet desetinných míst a velikost koeficientu exp, samotný výpočet probíhá s původními a a b.
Áno, ale tak, že namiesto a a b sú v samotnej operácii použité ich násobky, a výsledok podelený - teda výsledok môže byť odlišný.

Neříkám, že ne...
Myslel som to trochu všeobecnejšie. Napríklad, ak ide o financie, pracovať s najmenšou možnou jednotkou ako číslom 1. Alebo použiť vlastný formát uloženia čísla. Alebo, najjednoduchšie, a obvykle dostatočné, pracovať s dostupným formátom uloženia čísel v programovacom jazyku nejako vhodne.
Joker
Profil
SeparateSK:
Není důvod počet míst určovat dynamicky.
Prostě bych dal nějakou přesnost a na tu by se zaokrouhlovaly výsledky určené k zobrazení.

Jinak kdyby měla čísla být prezentovaná vždycky zarovnaná na nějaký počet desetinných míst (třeba 4 jako 4,00), šlo by jednoduše a přímočaře použít metodu toFixed.

Mimochodem, kontrolní otázka:
Jako metoda zaokrouhlení čísla se obvykle uvádí číslo vydělit přesností, zaokrouhlit (Math.round) a vynásobit přesností, kde „přesnost“ je 10^-(počet desetinných míst) .
Nějak takhle:
var PRESNOST = 0.001

function zaokrouhlit(cislo) {
  return Math.round(cislo/PRESNOST) * PRESNOST;
}

Bude to ale opravdu vždycky fungovat? Jelikož po zaokrouhlení je násobení reálným číslem, nevzniká tam zase ten samý problém?

Jako alternativa mě napadá použít toFixed a pak parseFloat abych se zbavil případných nul na konci, což by mělo fungovat vždycky, akorát ten postup (konvertovat číslo na řetězec a pak zpátky na číslo) vypadá trochu divně.
SeparateSK
Profil
Hm, ako zázrakom:
var PRESNOST = 0.001; 
function zaokrouhlit(cislo) {
  return Math.round(cislo/PRESNOST) * PRESNOST;
}
zaokrouhlit(0.1-.06-.04);
Dá správny výsledok,teda 0 a takmer isto aj toFixed (toFixed vráti string) až na to, že zaokrouhlit(.1111111111) vráti 0.1111111111 a nie 0.111, inak to funguje celkom dobre.
Joker
Profil
SeparateSK:
až na to, že zaokrouhlit(.1111111111) vráti 0.1111111111 a nie 0.111, inak to funguje celkom dobre.
Nevrátí, zaokrouhlit(.1111111111) mi správně vrací 0.111.
Chamurappi
Profil
Reaguji na Jokera:
Zrovna píšu povídání, které se nepřesnosti čísel věnuje, a tvůj příspěvek [#4] mi slouží jako vzor.

U výrazů počítajících s reálnými čísly preferujte zápis, který v jednotlivých krocích výpočtu pokud možno nedává extrémně vysoké nebo extrémně nízké mezivýsledky
Nedomnívám se, že při násobení toto hraje roli. Myslím, že nepřesnost, která tímto způsobem vznikne, nemůže být nijak zásadní a že tedy spadá spíš do množiny problémů s výpisem nebo s porovnáním. Větší škody může napáchat opakované sčítání.
Takže rada „zkuste při násobení proházet činitele“ je sice dobrá, ale patří spíš k prevenci (stejně jako [#5]), neupozorňuje na jiný druh špatných důsledků.

Nenapadá mě žádný realistický scénář, kdy by porouchaný mezivýsledek opravdu významně poškodil celý výpočet a kde by šlo vymyslet přesné alternativní řešení. (Což by byl opravdu třetí druh možného problému.)
Jan Tvrdík
Profil
Chamurappi:
Nenapadá mě žádný realistický scénář
Nejde jenom o násobení, jde obecně o to, že zvolené pořadí operací je důležité, ač z pohledu matematiky je to jedno.
Příklad: Výraz n * (n - 1) je vždy bezpečně dělitelný dvěma. To ale nemůžeme říct od jednotlivých činitelích. To v důsledku znamená, že n * (n - 1) / 2 lze bezpečně spočítat s celými čísly, ale např. n / 2 * (n - 1) už nikoliv.
_es
Profil
Jan Tvrdík:
n * (n - 1) / 2 lze bezpečně spočítat s celými čísly, ale např. n / 2 * (n - 1) už nikoliv.
V tomto prípade to je jedno, lebo sa delí dvomi, respektíve mocninou dvoch. No aj pri rovnakom probléme s násobením a delením iným číslom ako mocninou dvoch by často skôr došlo k vyrušeniu necelého „smetia“ čísla a ak nie, stačí výsledok vhodne zaokrúhliť. Vážnejšie problémy môžu nastať pri sčitovaní a odčitovaní rádovo odlišných čísel.
Jan Tvrdík
Profil
_es:
Neznám implementační detaily JS, ale v PHP i C++ to povede na jiný výsledek.
Chamurappi
Profil
Reaguji na Jana Tvrdíka:
jde obecně o to, že zvolené pořadí operací je důležité
Ano, je důležité, stejně jako je důležité se desetinným číslům vyhnout, je-li to možné. Ale není to třetí druh problému způsobený nepřesností čísel. Při násobení se nepřesnost projevit může, ale chyba se nenabaluje.
Mimochodem, pro jaké n neplatí n * (n - 1) / 2 == n / 2 * (n - 1)? JavaScript používá stejný double jako PHP a C++ (možná až na NaNy).

Třetí druh problému by šel vyvolat tím, že k Math.pow(2, 53) přičtu milionkrát jedničku. Přičítání s tím číslem vůbec nehne, tudíž dostanu číslo o milion menší, než kdybych nejprve sečetl milion jedniček a pak k nim přičetl Math.pow(2, 53). Ale to mi připadá jako taková hodně umělá situace, nepřesnost celých čísel nad Math.pow(2, 53) už sama o sobě poškozuje výpočty a navíc se stejně pohybujeme v šestnácticiferných hodnotách, kde ten milionový rozdíl není až tak významný. Na to, aby rozdíl byl názorný, bych musel těch jedniček přičíst mnohem víc a to už by zase v praxi trvalo nesnesitelně dlouho, sama pomalost takové činnosti by představovala výrazný problém, nehledě na nepřesnost. Jak jsem již psal, realistický scénář, kdy se mikroskopická nepřesnost projeví v makroskopickém měřítku, mě nenapadá…
« 1 2 »

Vaše odpověď

Mohlo by se hodit

Neumíte-li správně určit příčinu chyby, vkládejte odkazy na živé ukázky.
Užíváte-li nějakou cizí knihovnu, ukažte odpovídajícím, kde jste ji vzali.

Užitečné odkazy:

Prosím používejte diakritiku a interpunkci.

Ochrana proti spamu. Napište prosím číslo dvě-sta čtyřicet-sedm:

0