Autor Zpráva
quarantine
Profil *
Zdravím používám pro výpočet haléřového dorovnání následující vzorec:
(castka zaokrouhlena na cele cislo pomocí funkce round) - (castka zaokrouhlena na 2 desetinna mista pomoci funkce number_format)

vše se do teď jevilo v pořádku, ovšem dnes jsem narazil na problém, kdy je částka 4309,4997 a funkce round ji zaokrouhlí na 4309 místo správného zaokrouhlení na 4310. Vím proč to vzniká, jelikož čísla za desetinnou čárkou ,4997 nejsou vetší než ,5 a tudíž se zaokrouhlí dolů, ale jak toto vyřešit? Napadlo mě nejdříve zakrouhlit na 4309,5 a poté podruhé, ale zase nevím, aby to neovlivnilo ostatní případy, které to doposud počítalo správně.
Dan Charousek
Profil
quarantine:
castka zaokrouhlena na 2 desetinna mista pomoci funkce number_format

Pokud vím, tak number_format nezaokrouhluje (případně mě někdo prosím opravte).
Na dvě desetinná bys měl zaokrouhlovat také pomocí funkce rand.

Co je tedy cílem? Zjistit počet haléřů?

$castka =  4309,4997
echo abs(round($castka) - round($castka, 2)); // vypíše 0.5

edit: Beru zpět, nebude fungovat, pro čísla s desetinným místem nad 0.5

$castka = 4309.3;
echo round($castka, 2) - floor($castka);

Musíš si dát ale pozor, protože výsledky nebudou přesné viz.: Diskuse JPW: Časté potíže, zajímavosti a poučné debaty » Výsledkem výpočtu 1 - 0.9 není přesně 0.1
quarantine
Profil *
Omlouvám se za nepřesné vyjádření. Jde mi o to zaokrouhlit částku 4309,4997 na 4310 a zároveň získat hodnotu o kolik se zaokrouhlilo (tj. 0,5), naopak částku 4309,4497 by to mělo zaokrouhlit na 4109 a hodnota o kolik se zaokrouhlilo by měla být 0,4.
Alphard
Profil
quarantine [#1]:
Tohle je problematické. On ani ten nápad se zaokrouhlením na 4309.5 nemusí obecně fungovat, protože dané číslo nemusí být přesně reprezentovatelné. Tj. i po zaokrouhlení to může být .499999....
Řešením je použít datový typ, který pracuje s určitou desetinnou přesností a těmito problémy netrpí. Třeba v MySQL je decimal, který se pro uložení těchto hodnot perfektně hodí. V PHP samostném je to bohužel dost problém. Některé systémy používají workaround, že částku násobí 100*, drží ji jako integer, provádí operace s integer (ty jsou samozřejmě zcela přesné) a výstup pak formátují dle daných pravidel.
* Haléřová přesnost, lze nastavit jakkoliv jinak.

Tolik k vysvětlení principu. Můžete si to tak naimplementovat sám, ale přímočařejší bude využít knihovnu BC Math. V tomto případě jsou jednotlivé operandy udržovány jako řetězce a jako matematické operátory se používají funkce této knihovny (PHP neumí přetěžovat operátory). Určitý problém je, že tato knihovna nemá funkci pro zaokrouhlování, takže si ji musíte napsat sám. Můžete ji najít v komentářích, ale pozor, tato verze chybně zaokrouhluje záporná čísla.

Když pak budu mít výrobek 1 za cenu např. 2208.15 a výrobek 2 za cenu 2101.35, budu jejich ceny reprezentovat takto:
$price1 = '2208.15';
$price2 = '2101.35';
jejich soucet získám pomocí BC Math knihovny
$sum = bcadd($price1, $price2, 2); // což je string s přesnou hodnotou 4309.50
Pak mohu pomocí funkce z komentářů provést zaokrouhlení
$rounded = bcround($sum); // tohle je 4310
a zjistit rozdíl
$diff = bcsub($rounded, $sum, 2); // tady je přesně 0.50

Bude to fungovat dobře, ale nikdy nesmíte provést konverzi na double. Tzn. v databázi ukládat jako decimal a v PHP jako stringy.

Oprava, v posledním výrazu jsem měl chybu, sám jsem použil operátor - místo funkce bcsub :-) Pozor na to, člověk opravdu automatizovaně píše operátory, ale jediná taková chyba to může celé rozbít (na PHP mě mrzí, že v tomto případě provede implicitní konverzi misto toho, aby hlásilo špatný datový typ).
quarantine
Profil *
Alphard:
To je přesně to, co jsem hledal, děkuji moc.

Vaše odpověď

Mohlo by se hodit


Prosím používejte diakritiku a interpunkci.

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