Обсуждение: math error or rounding problem Money type

Поиск
Список
Период
Сортировка

math error or rounding problem Money type

От
Justin
Дата:
I believe i have found a math bug/rounding problem with Money type when its used with SUM()...  Postgresql 8.3.1<br
/><br/> --------------- Background on the Problem--------------------<br /><br /> We have gl_trans table with 92,000
rowswith one column containing the positive and negative entries.<br /><br /> In order to make this gl_trans table make
moresense and to group the accounts in correct debits and credits along with type of accounts, A view was created that
doesgrouping and sorting.   To further make things easier the view casted the results into the Money Type just to make
theselect statements that call the view shorter.<br /><br /> All looked great for several weeks till all of sudden the
sumedvalues for all accounts goes out by 0.01.  <br /><br /> I needed to confirm this was a rounding problem and not  a
GLentry that was bad.  ( if we had a bad entry this would scream we have a far bigger problem where the application
allowedan GL entry to be committed that was out of balance)<br /><br /> To confirm that all entries made have equal and
oppositeentry  below select statement was created.  The  gltrans_sequence column is integer key that groups General
Ledgerentries together so all the sides of a specific entry can be found.  <br /><br /> select * <br /> from <br />
(selectgltrans_sequence, sum(gltrans_amount) as Neg from gltrans  where gltrans_amount < 0 group by
gltrans_sequence)as neg,<br /> (select gltrans_sequence, sum(gltrans_amount) as pos from gltrans  where gltrans_amount
>0 group by gltrans_sequence) as pos<br /> where neg.gltrans_sequence = pos.gltrans_sequence <br /> and neg.neg
<>pos.pos*-1<br /><br /> This returns no records as expected...<br /><br /> Now armed with that no entry was bad
Isuspected it had to be with the money data type.<br /> So I added explicit castings <br /><font color="#3366ff"><br />
select* <br /> from <br /> (select gltrans_sequence, sum(gltrans_amount::text::money) as Neg from gltrans  where
gltrans_amount< 0 group by gltrans_sequence) as neg,<br /> (select gltrans_sequence,
sum(gltrans_amount::text::money)as pos from gltrans  where gltrans_amount > 0 group by gltrans_sequence) as pos<br
/>where neg.gltrans_sequence = pos.gltrans_sequence <br /> and neg.neg <> pos.pos*-1</font><br />
----------------<br/><font color="#3366ff">select * <br /> from <br /> (select gltrans_sequence,
sum(gltrans_amount::text::money)as Neg from gltrans  where gltrans_amount < 0 group by gltrans_sequence) as neg,<br
/>(select gltrans_sequence, sum(gltrans_amount::text::money) as pos from gltrans  where gltrans_amount > 0 group by
gltrans_sequence)as pos<br /> where neg.gltrans_sequence = pos.gltrans_sequence <br /> and neg.neg::text::money
<>pos.pos::text::money*-1</font><br /> -------------<br /><font color="#3366ff">select * <br /> from <br />
(selectgltrans_sequence, sum(gltrans_amount) as Neg from gltrans  where gltrans_amount < 0 group by
gltrans_sequence)as neg,<br /> (select gltrans_sequence, sum(gltrans_amount) as pos from gltrans  where gltrans_amount
>0 group by gltrans_sequence) as pos<br /> where neg.gltrans_sequence = pos.gltrans_sequence <br /> and
neg.neg::text::money<> pos.pos::text::money*-1</font><br /><br /> -------------------<br />  Nothing  resulted in
showinga entry that was out of balance.  <br /><br /><br /><br /><br /><br /> ----------------------Identifying the
problem---------------------------<br /><br /> So i turned my attention to the view which casted numeric type to
Money. View is called trailbalance <br /><br /> ------------The Bad Select Statement that creates the View
--------------<br/><font color="#3366ff">SELECT p.period_id, p.period_start, p.period_end, a.accnt_id,<br />       
a.accnt_number,a.accnt_descrip, p.period_yearperiod_id,<br />        a.accnt_type,<br />        SUM(CASE WHEN
g.gltrans_date< p.period_start<br />                 THEN g.gltrans_amount ELSE 0.0<br />           
END)::text::moneyAS beginbalance,<br />        SUM(CASE WHEN g.gltrans_date <= p.period_end<br />                 
ANDg.gltrans_date >= p.period_start<br />                  AND g.gltrans_amount <= 0::numeric<br />
               THEN g.gltrans_amount ELSE 0.0<br />            END)::text::money AS negative,<br />        SUM(CASE
WHENg.gltrans_date <= p.period_end<br />                  AND g.gltrans_date >= p.period_start<br />
                AND g.gltrans_amount >= 0::numeric<br />                 THEN g.gltrans_amount ELSE 0.0<br />
          END)::text::money AS positive,<br />        SUM(CASE WHEN g.gltrans_date <= p.period_end<br />
                AND g.gltrans_date >= p.period_start<br />                 THEN g.gltrans_amount ELSE 0.0<br />
          END)::text::money AS difference,<br />        SUM(CASE WHEN g.gltrans_date <= p.period_end<br />
               THEN g.gltrans_amount ELSE 0.0<br />            END)::text::money AS endbalance<br />   FROM period p<br
/> CROSS JOIN accnt a<br />   LEFT JOIN gltrans g ON (g.gltrans_accnt_id = a.accnt_id<br />                          
ANDg.gltrans_posted = true)<br />   where p.period_id = 58<br />  group by  p.period_id, p.period_start, p.period_end,
a.accnt_id,<br/>        a.accnt_number, a.accnt_descrip, p.period_yearperiod_id,<br />        a.accnt_type<br />  <br
/> ORDER BY p.period_id, a.accnt_number;</font><br /> ---------------End Select --------------------<br /><br /><br />
Thequery that calls this View  <br /><br /> ------------------<br /><font color="#3366ff">Select <br />     sum(
beginBalance) as beginbalance, <br />     sum( negative ) as debit, <br />     sum( positive ) as credit, <br />    
sum(difference ) as difference, <br />     sum( endbalance) as endbalance <br />  from trailbalance </font><br />
---------------------<br/><br /> Result is  <br /><br /><font color="#ff0000">-$0.01</font>    -$11,250,546.74   
$11,250,546.75 <fontcolor="#ff0000"> -$0.02</font>   <font color="#ff0000">-$0.01</font><br /><br /> This be wrong. <br
/><br/> Figuring it must be Money type dropped and recreated the view without the money casting. <br /><br />
------------TheFixed Select Statement that creates the View --------------<br /><font color="#3366ff">SELECT
p.period_id,p.period_start, p.period_end, a.accnt_id, a.accnt_number, a.accnt_descrip, p.period_yearperiod_id,
a.accnt_type,sum(<br />         CASE<br />             WHEN g.gltrans_date < p.period_start THEN g.gltrans_amount<br
/>            ELSE 0.0<br />         END) AS beginbalance, sum(<br />         CASE<br />             WHEN
g.gltrans_date<= p.period_end AND g.gltrans_date >= p.period_start AND g.gltrans_amount <= 0::numeric THEN
g.gltrans_amount<br/>             ELSE 0.0<br />         END) AS negative, sum(<br />         CASE<br />            
WHENg.gltrans_date <= p.period_end AND g.gltrans_date >= p.period_start AND g.gltrans_amount >= 0::numeric
THENg.gltrans_amount<br />             ELSE 0.0<br />         END) AS positive, sum(<br />         CASE<br />
           WHEN g.gltrans_date <= p.period_end AND g.gltrans_date >= p.period_start THEN g.gltrans_amount<br />
           ELSE 0.0<br />         END) AS difference, sum(<br />         CASE<br />             WHEN g.gltrans_date
<=p.period_end THEN g.gltrans_amount<br />             ELSE 0.0<br />         END) AS endbalance<br />    FROM
periodp<br />   CROSS JOIN accnt a<br />    LEFT JOIN gltrans g ON g.gltrans_accnt_id = a.accnt_id AND g.gltrans_posted
=true<br />   GROUP BY p.period_id, a.accnt_number, p.period_start, p.period_end, a.accnt_id, a.accnt_descrip,
p.period_yearperiod_id,a.accnt_type<br />   ORDER BY p.period_id, a.accnt_number;</font><br /> ---------------End
Select--------------------<br /><br /><br /> The above query results in this  which is what i would expect. <br /><br
/>0.00000000    -11250546.74375232     11250546.74375232     0.00000000     0.00000000<br /><br /><br /> Now knowing
forsure its in Money type casting i do this select statement <br /> ----------------------<br /><font
color="#3333ff">Select<br />     '2',<br />     sum( beginBalance )::text::money as beginbalance, <br />     sum(
negative)::text::money as debit, <br />     sum( positive )::text::money as credit, <br />     sum( difference
)::text::moneyas difference, <br />     sum( endbalance)::text::money as endbalance <br />  from trailbalance <br />
 union<br />  Select <br />     '1',<br />     sum( beginBalance::text::money) as beginbalance, <br />     sum(
negative::text::money)as debit, <br />     sum( positive::text::money) as credit, <br />     sum(
difference::text::money)as difference, <br />     sum( endbalance::text::money) as endbalance <br />  from trailbalance
</font><br/> -------------------------<br /><br /> The results I think very interesting <br /> "1"  <font
color="#ff0000">-$0.01</font>  -$11,250,546.74     $11,250,546.75   <font color="#ff0000">-$0.02;</font>   <font
color="#ff0000">-$0.01</font><br/> "2"  $0.00    -$11,250,546.74     $11,250,546.74    $0.00      $0.00<br /><br /> As
youcan see  casting to money before sum() is called are incorrect<br /><br /> Can anyone else confirm this odd behavior
whencasting to Money type.<br /><br /> Thank you for your time and patience reading this long post....<br /><br /><br
/><br/> 

Re: math error or rounding problem Money type

От
Tom Lane
Дата:
Justin <justin@emproshunts.com> writes:
> I believe i have found a math bug/rounding problem with Money type when 
> its used with SUM()...  Postgresql 8.3.1

You do know that money only stores two fractional digits?
        regards, tom lane


Re: math error or rounding problem Money type

От
Justin
Дата:
<br /><br /> Tom Lane wrote: <blockquote cite="mid:24505.1212877614@sss.pgh.pa.us" type="cite"><pre wrap="">Justin <a
class="moz-txt-link-rfc2396E"href="mailto:justin@emproshunts.com"><justin@emproshunts.com></a> writes:
</pre><blockquotetype="cite"><pre wrap="">I believe i have found a math bug/rounding problem with Money type when 
 
its used with SUM()...  Postgresql 8.3.1   </pre></blockquote><pre wrap="">
You do know that money only stores two fractional digits?
        regards, tom lane </pre></blockquote> yes.  The question is why are the to sides not equal anymore <br /><br />
Takethis <br /><br /> Select <br />     '2',<br />     round(sum( beginBalance ),6) as beginbalance, <br />    
round(sum(negative ),6) as debit, <br />     round(sum( positive ),6) as credit, <br />     round(sum( difference ),6)
asdifference, <br />     round(sum( endbalance),6) as endbalance <br />  from trailbalance <br />  union <br />  Select
<br/>     '1',<br />     sum( round(beginBalance,6)) as beginbalance, <br />     sum( round(negative,6)) as debit, <br
/>    sum( round(positive,6)) as credit, <br />     sum( round(difference,6)) as difference, <br />     sum(
round(endbalance,6))as endbalance <br />  from trailbalance <br /><br /> "1"  -0.000006   -11250546.743752   
11250546.743752  0.000000   -0.000007<br /> "2"   0.000000    -11250546.743752   11250546.743752   0.000000   
0.000000<br/><br /> At the very least this show a clear warning when rounding do it after all the sum function is
callednot before.  <br /><br /> 

Re: math error or rounding problem Money type

От
Mark Kirkwood
Дата:
Justin wrote:
> yes.  The question is why are the to sides not equal anymore
>
> Take this
>
> Select
>     '2',
>     round(sum( beginBalance ),6) as beginbalance,
>     round(sum( negative ),6) as debit,
>     round(sum( positive ),6) as credit,
>     round(sum( difference ),6) as difference,
>     round(sum( endbalance),6) as endbalance
>  from trailbalance
>  union
>  Select
>     '1',
>     sum( round(beginBalance,6)) as beginbalance,
>     sum( round(negative,6)) as debit,
>     sum( round(positive,6)) as credit,
>     sum( round(difference,6)) as difference,
>     sum( round(endbalance,6)) as endbalance
>  from trailbalance
>
> "1"  -0.000006   -11250546.743752    11250546.743752   0.000000   
> -0.000007
> "2"   0.000000    -11250546.743752   11250546.743752   0.000000    
> 0.000000
>
> At the very least this show a clear warning when rounding do it after 
> all the sum function is called not before. 
>

IFAIK (dimly recalling numerical analysis courses at university) SUM and 
ROUND can *never* be commuted. In general the recommended approach is to 
round as late as possible and as few times are possible - so your 1st 
query is the correct or best way to go.

Cheers

Mark


Re: math error or rounding problem Money type

От
Gregory Stark
Дата:
"Mark Kirkwood" <markir@paradise.net.nz> writes:

> IFAIK (dimly recalling numerical analysis courses at university) SUM and ROUND
> can *never* be commuted. In general the recommended approach is to round as
> late as possible and as few times are possible - so your 1st query is the
> correct or best way to go.

I don't think "as late as possible" applies with money. If you were dealing
with approximate measurements you want to round as late as possible because
rounding is throwing away precision. But if you're dealing with money you're
dealing with exact quantities. 

There is only going to be one correct time to round and that's whenever you're
creating an actual ledger item or order line item or whatever. Once you've
calculated how much interest to credit or whatever you have to make that
credit an exact number of cents and the $0.004 you lost or gained in rounding
never comes up again.

--  Gregory Stark EnterpriseDB          http://www.enterprisedb.com Ask me about EnterpriseDB's PostGIS support!


Re: math error or rounding problem Money type

От
Jan Urbański
Дата:
Gregory Stark wrote:
> "Mark Kirkwood" <markir@paradise.net.nz> writes:
> 
>> IFAIK (dimly recalling numerical analysis courses at university) SUM and ROUND
>> can *never* be commuted. In general the recommended approach is to round as
>> late as possible and as few times are possible - so your 1st query is the
>> correct or best way to go.

Justin, isn't your problem related precisely to what Tom said?

Now, when you're casting to Money, you're doing a cast like that 
original_type -> text -> money (that's from your trailbalance view). I 
suspect the original_type is NUMERIC (and I think it's a very good type 
to keep your monetary data in).
My guess: what happens is that you have numbers with more that 6 
fractional digits in your original table, and they're kept as NUMERIC 
values. If you round them to the 6th fractional digit *before* summing 
them up, you can indeed get different results from what you'd get if 
you'd rounded them *after* doign the sum.

Compare:

=# select round(0.0000004 + 0.0000004, 6) ;  round
---------- 0.000001
(1 row)

=# select round(0.0000004, 6) + round(0.0000004) ; ?column?
---------- 0.000000

Do you see what (could've) happened? The first query is computed like this:
round(0.0000004 + 0.0000004, 0) => round(0.0000008, 6) => 0.000001
whereas the second one is more like:
round(0.0000004, 6) + round(0.0000004, 6) => 0.000000 + 0.000000 => 0.000000

Fractional parts that have been thrown away by the rounding may, when 
added up, become fractional parts that get significant when you're 
calculating the rounded value of the sum.

So yes, probably the way to go is do *all* computations in NUMERIC and 
only cast when you're about to generate a report or present the data to 
the end user. Otherwise you risk losing some cents like that (and you 
need to be aware that a cast to MONEY *is* in fact a truncation, and you 
will not get mathematically correct results).

Cheers,
Jan

-- 
Jan Urbanski
GPG key ID: E583D7D2

ouden estin


Re: math error or rounding problem Money type

От
"Merlin Moncure"
Дата:
On 6/8/08, Gregory Stark <stark@enterprisedb.com> wrote:
> I don't think "as late as possible" applies with money. If you were dealing
> with approximate measurements you want to round as late as possible because
> rounding is throwing away precision. But if you're dealing with money you're
> dealing with exact quantities.
>
> There is only going to be one correct time to round and that's whenever you're
> creating an actual ledger item or order line item or whatever. Once you've
> calculated how much interest to credit or whatever you have to make that
> credit an exact number of cents and the $0.004 you lost or gained in rounding
> never comes up again.

Completely correct.  In a proper accounting system you can only pull
from a very limited subset of arithmetic operations.  'rounding' is
not one of them except in the special case you mention above.

merlin