PostgreSQL, funkce round() a 666
17. května 2006
Web design
To mě zase jednou sloník PgSQL pěkně převezl.. Program automatizace přiřazování plateb k fakturám pracoval bezproblémově, než došlo na fakturu s částkou 666 bez DPH, což je s 19% daní 792,54. Funkce vyvolaná triggerem na insert platbu nespárovala.. Kde je chyba?
Ceny s DPH zaokrouhluji klasickým způsobem, tedy na celá čísla k nejbližší hodnotě (72,5 je 73 apod.) a právě při zaokrouhlení došlo k problému.
round(SUM(price*(tax+100)/100)) INTO not_paid
Jakou myslíte, že bude mít proměnná not_paid hodnotu, pokud je price=666 a tax=19? 792, ale proč? Pokud rozšíříme zaokrouhlení na dvě desetinná místa, tak dostaneme odpovědi 792,00, takže musí docházet k zaokrouhlení již někde dříve.. Myslel jsem, že při tom dělení stem, ale nakonec se ukázalo, že při tom násobení (samozřejmě, že ne, že to zapříčiňuje dělení), proto stačí doplnit definici numeric(v,s), přičemž v definuje počet číslic v čísle a s přesnost desetinných míst. Doplněno: Nebo, jak psal Andrew v komentářích, nepřetypovávat a jedno z čísel, např. 100 napsat desetinně a výsledek bude s desetinnou čárkou.
round(SUM(price*(tax+100)::numeric(10,2)/100)) INTO not_paid
Pro mé účely jsem použil výše zmíněnou syntaxi a vše již běhá v pořádku a aspoň vím, že si někdy dokáže Postgre leccos domyslet a převést, bohužel v tomto případě na integer..
Aktualizováno:
Ani si nikdo nevšiml, že nadpis článku proklamoval funkci rand, přitom byl text o round, adresu spotu nechávám starou, je již indexována a nahodilí hledači by se nemuseli k tématu dostat.. ;o)
Andrew 18.5.06 09:29
Předně si dovolím upozornit, že v násobení zcela jistě chyba není. Jsouce oba činitelé celočíselní, bude i výsledek celočíselný. IMHO předvídatelně ;) Co mě překvapuje víc, je, že PgSQL u dělení automaticky nezmění typ výsledku. Třeba v opovrhovaném MySQL to funguje podle očekávání.
Nahodil jsem PgSQL (8.1) a jal se bádat. A zjistil jsem, že aby to fungovalo, jak má, je potřeba, aby alespoň jedno číslo ve vzorci bylo desetinné. Tzn. řešení je např toto: round(SUM(price*(tax+100.0)/100)) INTO not_paid, což je IMHO mnohem přehlednější a elegantnější než to přetypování.