WIP: rewrite numeric division

Started by Tom Laneabout 19 years ago16 messagespatches
Jump to latest
#1Tom Lane
tgl@sss.pgh.pa.us

I wrote:

I just blew the dust off my old copy of Knuth vol 2, and see that his
algorithm for multi-precision division generates output digits that are
correct to start with (or at least he never needs to revisit a digit
after moving on to the next). ISTM we should go over to an approach
like that.

The attached proposed patch rewrites div_var() using Knuth's algorithm,
meaning that division should always produce an exact truncated output
when asked to truncate at X number of places. This passes regression
tests and fixes both of the cases previously exhibited:
http://archives.postgresql.org/pgsql-bugs/2007-06/msg00068.php
http://archives.postgresql.org/pgsql-general/2005-05/msg01109.php

The bad news is that it's significantly slower than the CVS-HEAD code;
it appears that for long inputs, div_var is something like 4X slower
than before, depending on platform. The numeric_big regression test
takes about twice as long as before on one of my machines, and 50%
longer on another. This is because the innermost loop now involves
integer division, which it didn't before. (According to oprofile,
just about all the time goes into the loop that subtracts qhat * divisor
from the working dividend, which is what you'd expect.)

Now it's unlikely that real-world applications are going to be as
dependent on the speed of div_var for long inputs as numeric_big is.
And getting the right answer has to take priority over speed anyway.
Still this is a bit annoying. Anyone see a way to speed it up, or
have another approach?

regards, tom lane

#2Bruce Momjian
bruce@momjian.us
In reply to: Tom Lane (#1)
Re: WIP: rewrite numeric division

"Tom Lane" <tgl@sss.pgh.pa.us> writes:

The bad news is that it's significantly slower than the CVS-HEAD code;
it appears that for long inputs, div_var is something like 4X slower
than before, depending on platform.

Where did we get the CVS-HEAD algorithm from anyways? wikipedia lists a whole
bunch of multiplication algorithms -- none of which sound like this -- but
only two division algorithms, "school-book" which is O(n^2) and Newton's
Method which has complexity equal to the multiplication method used.

I'm not sure I see how to apply Newton's Method and get such low complexity
though. It seems to me like you would have to repeatedly multiply so you
should get an additional factor in the complexity representing the number of
iterations necessary.

Still this is a bit annoying. Anyone see a way to speed it up, or
have another approach?

What order of complexity is this algorithm? It looks basically like O(n^2)
school-book division or is there something more clever going on?

--
Gregory Stark
EnterpriseDB http://www.enterprisedb.com

#3Tom Lane
tgl@sss.pgh.pa.us
In reply to: Bruce Momjian (#2)
Re: WIP: rewrite numeric division

Gregory Stark <stark@enterprisedb.com> writes:

Where did we get the CVS-HEAD algorithm from anyways?

IIRC it's from Smith's "FM" library cited in numeric.c, which is also
where most of numeric.c's higher-order-function algorithms came from.
It's good code but oriented to scientific computing, which means it
thinks that a close approximation to the right answer is good enough.
In hindsight, there are too many people out there who expect div and mod
to be exact for us to go with that approach.

wikipedia lists a whole
bunch of multiplication algorithms -- none of which sound like this -- but
only two division algorithms, "school-book" which is O(n^2) and Newton's
Method which has complexity equal to the multiplication method used.

Yeah, my proposed patch is schoolbook division. I don't think I'd trust
Newton's method to give anything but a close approximation, which is
what we have in HEAD already.

regards, tom lane

#4Bruce Momjian
bruce@momjian.us
In reply to: Tom Lane (#3)
Re: WIP: rewrite numeric division

"Tom Lane" <tgl@sss.pgh.pa.us> writes:

Yeah, my proposed patch is schoolbook division. I don't think I'd trust
Newton's method to give anything but a close approximation, which is
what we have in HEAD already.

Well unless we start storing rational numbers it'll always be a limited to a
finite number of decimals. The key is whether we can guarantee a lower bound
on the number of accurate decimal places. As long as we can then we can keep
going until the decimal places we're going to return are all accurate.

So your complaint about the existing code boils down to not having any
rigorous way to know when to stop. I don't think Newton's method has that
problem, at least not for simple polynomials. Any digits which don't change
from one iteration to the next are definitely correct.

The problem with Newton's method is that it needs a fast multiplication
algorithm. Looking at it now though it looks like our multiplication algorithm
is similar to the old division algorithm which looks like an optimized
schoolbook multiplication.

It looks like multiplication can also generate incorrect results. Because it
rounds to rscale and then apply_typmod will round again. So a number like 2.49
could conceivably round up to 3 if the two roundings happen to hit at the
wrong place.

--
Gregory Stark
EnterpriseDB http://www.enterprisedb.com

#5Bruce Momjian
bruce@momjian.us
In reply to: Bruce Momjian (#4)
Re: WIP: rewrite numeric division

"Gregory Stark" <stark@enterprisedb.com> writes:

So your complaint about the existing code boils down to not having any
rigorous way to know when to stop. I don't think Newton's method has that
problem, at least not for simple polynomials. Any digits which don't change
from one iteration to the next are definitely correct.

Er, I don't think that last sentence is correct. But I believe it is possible
to tell how many digits are accurate based on the difference between
successive estimates because you know how fast it converges (quadratically).

--
Gregory Stark
EnterpriseDB http://www.enterprisedb.com

#6Tom Lane
tgl@sss.pgh.pa.us
In reply to: Bruce Momjian (#4)
Re: WIP: rewrite numeric division

Gregory Stark <stark@enterprisedb.com> writes:

"Tom Lane" <tgl@sss.pgh.pa.us> writes:

Yeah, my proposed patch is schoolbook division. I don't think I'd trust
Newton's method to give anything but a close approximation, which is
what we have in HEAD already.

Well unless we start storing rational numbers it'll always be a limited to a
finite number of decimals. The key is whether we can guarantee a lower bound
on the number of accurate decimal places. As long as we can then we can keep
going until the decimal places we're going to return are all accurate.

This is nonsense. Consider an approximate division that gives

...123.999999999999999999...

You can keep generating 9's forever, but you'll never know whether the
accurate result of trunc() should be 123 or 124. Unless you use a
method that gives you trustworthy digits to start with.

It looks like multiplication can also generate incorrect results.

It can, but only if told to produce fewer digits than would be in the
exact result, so I'm not worried about that.

regards, tom lane

#7Michael Paesold
mpaesold@gmx.at
In reply to: Tom Lane (#1)
Re: WIP: rewrite numeric division

Tom Lane wrote:

I wrote:

...

Now it's unlikely that real-world applications are going to be as
dependent on the speed of div_var for long inputs as numeric_big is.
And getting the right answer has to take priority over speed anyway.
Still this is a bit annoying. Anyone see a way to speed it up, or
have another approach?

regards, tom lane

+1 for the change from me.

We use PostgreSQL for financial accounting stuff, including plpgsql
triggers and functions etc. And we use numeric for all that. I always
thought that numeric division was exact! :-) I never saw problem,
perhaps because we round to very few digits, but well.

Please apply this patch. I can accept the performance drop, as long as
there won't be bad surprises with correctness.

Best Regards
Michael Paesold

#8Bruce Momjian
bruce@momjian.us
In reply to: Tom Lane (#1)
Re: WIP: rewrite numeric division

Because this has not been applied, this has been saved for the 8.4 release:

http://momjian.postgresql.org/cgi-bin/pgpatches_hold

---------------------------------------------------------------------------

Tom Lane wrote:

I wrote:

I just blew the dust off my old copy of Knuth vol 2, and see that his
algorithm for multi-precision division generates output digits that are
correct to start with (or at least he never needs to revisit a digit
after moving on to the next). ISTM we should go over to an approach
like that.

The attached proposed patch rewrites div_var() using Knuth's algorithm,
meaning that division should always produce an exact truncated output
when asked to truncate at X number of places. This passes regression
tests and fixes both of the cases previously exhibited:
http://archives.postgresql.org/pgsql-bugs/2007-06/msg00068.php
http://archives.postgresql.org/pgsql-general/2005-05/msg01109.php

The bad news is that it's significantly slower than the CVS-HEAD code;
it appears that for long inputs, div_var is something like 4X slower
than before, depending on platform. The numeric_big regression test
takes about twice as long as before on one of my machines, and 50%
longer on another. This is because the innermost loop now involves
integer division, which it didn't before. (According to oprofile,
just about all the time goes into the loop that subtracts qhat * divisor
from the working dividend, which is what you'd expect.)

Now it's unlikely that real-world applications are going to be as
dependent on the speed of div_var for long inputs as numeric_big is.
And getting the right answer has to take priority over speed anyway.
Still this is a bit annoying. Anyone see a way to speed it up, or
have another approach?

regards, tom lane

Content-Description: numeric-div.patch.gz

[ Type application/octet-stream treated as attachment, skipping... ]

---------------------------(end of broadcast)---------------------------
TIP 3: Have you checked our extensive FAQ?

http://www.postgresql.org/docs/faq

--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://www.enterprisedb.com

+ If your life is a hard drive, Christ can be your backup. +

#9Michael Paesold
mpaesold@gmx.at
In reply to: Bruce Momjian (#8)
Re: WIP: rewrite numeric division

Please, let's revisit this, and not postpone it without further
discussion. I never knew about the correctness issues in div_var(), but
since I know about it, I feel I am just waiting until that problem will
hit me or anyone else.
So can you, Tom, please describe in what situations the old code is
really unsafe?

We usually *round* all values to at maximum 4 decimal places -- are we
on the save side? Does this only affect high precision division, or any
divisions?

Best Regards
Michael Paesold

Bruce Momjian wrote:

Show quoted text

Because this has not been applied, this has been saved for the 8.4 release:

http://momjian.postgresql.org/cgi-bin/pgpatches_hold

---------------------------------------------------------------------------

Tom Lane wrote:

I wrote:

I just blew the dust off my old copy of Knuth vol 2, and see that his
algorithm for multi-precision division generates output digits that are
correct to start with (or at least he never needs to revisit a digit
after moving on to the next). ISTM we should go over to an approach
like that.

The attached proposed patch rewrites div_var() using Knuth's algorithm,
meaning that division should always produce an exact truncated output
when asked to truncate at X number of places. This passes regression
tests and fixes both of the cases previously exhibited:
http://archives.postgresql.org/pgsql-bugs/2007-06/msg00068.php
http://archives.postgresql.org/pgsql-general/2005-05/msg01109.php

The bad news is that it's significantly slower than the CVS-HEAD code;
it appears that for long inputs, div_var is something like 4X slower
than before, depending on platform. The numeric_big regression test
takes about twice as long as before on one of my machines, and 50%
longer on another. This is because the innermost loop now involves
integer division, which it didn't before. (According to oprofile,
just about all the time goes into the loop that subtracts qhat * divisor
from the working dividend, which is what you'd expect.)

Now it's unlikely that real-world applications are going to be as
dependent on the speed of div_var for long inputs as numeric_big is.
And getting the right answer has to take priority over speed anyway.
Still this is a bit annoying. Anyone see a way to speed it up, or
have another approach?

regards, tom lane

Content-Description: numeric-div.patch.gz

[ Type application/octet-stream treated as attachment, skipping... ]

#10Tom Lane
tgl@sss.pgh.pa.us
In reply to: Michael Paesold (#9)
Re: WIP: rewrite numeric division

Michael Paesold <mpaesold@gmx.at> writes:

Please, let's revisit this, and not postpone it without further
discussion. I never knew about the correctness issues in div_var(), but
since I know about it, I feel I am just waiting until that problem will
hit me or anyone else.

Yeah. I was basically waiting to see if anyone could come up with a
faster solution. Since no one seems to have an idea how to do it
better, I'm inclined to apply the patch for 8.3.

regards, tom lane

#11Bruce Momjian
bruce@momjian.us
In reply to: Tom Lane (#10)
Re: WIP: rewrite numeric division

"Tom Lane" <tgl@sss.pgh.pa.us> writes:

Michael Paesold <mpaesold@gmx.at> writes:

Please, let's revisit this, and not postpone it without further
discussion. I never knew about the correctness issues in div_var(), but
since I know about it, I feel I am just waiting until that problem will
hit me or anyone else.

Yeah. I was basically waiting to see if anyone could come up with a
faster solution. Since no one seems to have an idea how to do it
better, I'm inclined to apply the patch for 8.3.

My only reservation is that I don't have the numeric methods background to
tell if it's really necessary. My fix does resolve the only actual documented
inaccuracy in the existing method.

--
Gregory Stark
EnterpriseDB http://www.enterprisedb.com

#12Tom Lane
tgl@sss.pgh.pa.us
In reply to: Bruce Momjian (#11)
Re: WIP: rewrite numeric division

Gregory Stark <stark@enterprisedb.com> writes:

"Tom Lane" <tgl@sss.pgh.pa.us> writes:

Yeah. I was basically waiting to see if anyone could come up with a
faster solution. Since no one seems to have an idea how to do it
better, I'm inclined to apply the patch for 8.3.

My only reservation is that I don't have the numeric methods background to
tell if it's really necessary. My fix does resolve the only actual documented
inaccuracy in the existing method.

Well, this doesn't take a lot of numerical methods background: the
fundamental problem is that the existing code generates an *approximate*
answer, whereas people who are doing div and mod on large integers tend
to expect an *exact* answer. Approximate doesn't cut it --- there will
always be cases where an off-by-one-in-the-last-internal-place error can
carry far enough to the left to be visible to the user.

regards, tom lane

#13Bruce Momjian
bruce@momjian.us
In reply to: Tom Lane (#12)
Re: WIP: rewrite numeric division

"Tom Lane" <tgl@sss.pgh.pa.us> writes:

Gregory Stark <stark@enterprisedb.com> writes:

"Tom Lane" <tgl@sss.pgh.pa.us> writes:

Yeah. I was basically waiting to see if anyone could come up with a
faster solution. Since no one seems to have an idea how to do it
better, I'm inclined to apply the patch for 8.3.

My only reservation is that I don't have the numeric methods background to
tell if it's really necessary. My fix does resolve the only actual documented
inaccuracy in the existing method.

Well, this doesn't take a lot of numerical methods background: the
fundamental problem is that the existing code generates an *approximate*
answer, whereas people who are doing div and mod on large integers tend
to expect an *exact* answer. Approximate doesn't cut it --- there will
always be cases where an off-by-one-in-the-last-internal-place error can
carry far enough to the left to be visible to the user.

So does your code behave differently for this or does it round off to the
arbitrary division precision before hitting trunc?

postgres=# select trunc(99999999999999999999::numeric / 1000000000::numeric);
trunc
--------------
100000000000
(1 row)

--
Gregory Stark
EnterpriseDB http://www.enterprisedb.com

#14Tom Lane
tgl@sss.pgh.pa.us
In reply to: Bruce Momjian (#13)
Re: WIP: rewrite numeric division

Gregory Stark <stark@enterprisedb.com> writes:

"Tom Lane" <tgl@sss.pgh.pa.us> writes:

Well, this doesn't take a lot of numerical methods background: the
fundamental problem is that the existing code generates an *approximate*
answer, whereas people who are doing div and mod on large integers tend
to expect an *exact* answer. Approximate doesn't cut it --- there will
always be cases where an off-by-one-in-the-last-internal-place error can
carry far enough to the left to be visible to the user.

So does your code behave differently for this or does it round off to the
arbitrary division precision before hitting trunc?

postgres=# select trunc(99999999999999999999::numeric / 1000000000::numeric);
trunc
--------------
100000000000
(1 row)

No, my proposed patch doesn't change that. It might be that we should
provide an "integer division" operator for NUMERIC, so that you can get
at the exact result of trunc(x/y).

regards, tom lane

#15Bruce Momjian
bruce@momjian.us
In reply to: Tom Lane (#14)
Re: WIP: rewrite numeric division

"Tom Lane" <tgl@sss.pgh.pa.us> writes:

No, my proposed patch doesn't change that. It might be that we should
provide an "integer division" operator for NUMERIC, so that you can get
at the exact result of trunc(x/y).

I was also thinking that if the denominator had only factors of 2 and 5 we
could calculate the precision to be precisely enough to maintain the original
precision. Ie, nnnn/1000 should just give you n.nnn not n.nnn0000 and more
importantly it should never round.

--
Gregory Stark
EnterpriseDB http://www.enterprisedb.com

#16Bruce Momjian
bruce@momjian.us
In reply to: Tom Lane (#10)
Re: WIP: rewrite numeric division

This has been saved for the 8.4 release:

http://momjian.postgresql.org/cgi-bin/pgpatches_hold

---------------------------------------------------------------------------

Tom Lane wrote:

Michael Paesold <mpaesold@gmx.at> writes:

Please, let's revisit this, and not postpone it without further
discussion. I never knew about the correctness issues in div_var(), but
since I know about it, I feel I am just waiting until that problem will
hit me or anyone else.

Yeah. I was basically waiting to see if anyone could come up with a
faster solution. Since no one seems to have an idea how to do it
better, I'm inclined to apply the patch for 8.3.

regards, tom lane

--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://www.enterprisedb.com

+ If your life is a hard drive, Christ can be your backup. +