Proposal: Trigonometric functions in degrees

Started by Dean Rasheedabout 10 years ago75 messages
#1Dean Rasheed
dean.a.rasheed@gmail.com

Currently PostgreSQL only has trigonometric functions that work in
radians. I think it would be quite useful to have an equivalent set of
functions that worked in degrees. In other environments these are
commonly spelled sind(), cosd(), etc.

Partly, this would be a matter of convenience. It's quite common to
have a problem domain where angles are specified in degrees, and it's
somewhat cumbersome having to type things like sin(radians(x)) and
degrees(asin(x)).

Additionally, functions that worked natively in degrees would be able
to return exact answers in special cases like cosd(90) = 0, whereas
cos(radians(90)) is not exactly 0 because pi/2 cannot be represented
exactly as a floating point number.

Possibly the earthdistance module would benefit from these functions too.

Thoughts?

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#2Simon Riggs
simon@2ndQuadrant.com
In reply to: Dean Rasheed (#1)
Re: Proposal: Trigonometric functions in degrees

On 24 October 2015 at 05:24, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

Currently PostgreSQL only has trigonometric functions that work in
radians. I think it would be quite useful to have an equivalent set of
functions that worked in degrees. In other environments these are
commonly spelled sind(), cosd(), etc.

Partly, this would be a matter of convenience. It's quite common to
have a problem domain where angles are specified in degrees, and it's
somewhat cumbersome having to type things like sin(radians(x)) and
degrees(asin(x)).

Additionally, functions that worked natively in degrees would be able
to return exact answers in special cases like cosd(90) = 0, whereas
cos(radians(90)) is not exactly 0 because pi/2 cannot be represented
exactly as a floating point number.

That is important.

Possibly the earthdistance module would benefit from these functions too.

Thoughts?

+1, yes, please.

--
Simon Riggs http://www.2ndQuadrant.com/
<http://www.2ndquadrant.com/&gt;
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#3Emre Hasegeli
emre@hasegeli.com
In reply to: Dean Rasheed (#1)
Re: Proposal: Trigonometric functions in degrees

Currently PostgreSQL only has trigonometric functions that work in
radians. I think it would be quite useful to have an equivalent set of
functions that worked in degrees. In other environments these are
commonly spelled sind(), cosd(), etc.

I would prefer gradian over degree.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#4Michael Paquier
michael.paquier@gmail.com
In reply to: Emre Hasegeli (#3)
Re: Proposal: Trigonometric functions in degrees

On Sun, Oct 25, 2015 at 6:16 PM, Emre Hasegeli <emre@hasegeli.com> wrote:

Currently PostgreSQL only has trigonometric functions that work in
radians. I think it would be quite useful to have an equivalent set of
functions that worked in degrees. In other environments these are
commonly spelled sind(), cosd(), etc.

I would prefer gradian over degree.

This may be a matter of personal taste, but I am personally used to
degrees since primary school. By the way, +1 for the proposal.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#5David Fetter
david@fetter.org
In reply to: Emre Hasegeli (#3)
Re: Proposal: Trigonometric functions in degrees

On Sun, Oct 25, 2015 at 10:16:41AM +0100, Emre Hasegeli wrote:

Currently PostgreSQL only has trigonometric functions that work in
radians. I think it would be quite useful to have an equivalent set of
functions that worked in degrees. In other environments these are
commonly spelled sind(), cosd(), etc.

I would prefer gradian over degree.

We can have both, but degree is a good bit better known, which means
more users will care about it.

People have gone a long way toward dealing with problems like the
known correspondence (90° = π/2, etc.) and periodicity (f(x) =
f(x+360*n) for integer n, f in (sin, cos, tan, cot, sec, csc), for
example).

I haven't yet found same in a PostgreSQL-compatible library, though.

Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#6Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Emre Hasegeli (#3)
Re: Proposal: Trigonometric functions in degrees

On 25 October 2015 at 09:16, Emre Hasegeli <emre@hasegeli.com> wrote:

Currently PostgreSQL only has trigonometric functions that work in
radians. I think it would be quite useful to have an equivalent set of
functions that worked in degrees. In other environments these are
commonly spelled sind(), cosd(), etc.

I would prefer gradian over degree.

I think gradians are generally less commonly used than degrees and
radians, so I'm less inclined to include them.

Having degree-based functions would make it trivial to implement
user-defined gradian-based functions, just by multiplying or dividing
by 0.9, and they would return exact results in the smaller number of
cases where gradian results are exactly representable.

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#7Tom Lane
tgl@sss.pgh.pa.us
In reply to: Dean Rasheed (#6)
Re: Proposal: Trigonometric functions in degrees

Dean Rasheed <dean.a.rasheed@gmail.com> writes:

On 25 October 2015 at 09:16, Emre Hasegeli <emre@hasegeli.com> wrote:

I would prefer gradian over degree.

I think gradians are generally less commonly used than degrees and
radians, so I'm less inclined to include them.

I agree. gradians are not often used at all, AFAICT.

Having degree-based functions would make it trivial to implement
user-defined gradian-based functions, just by multiplying or dividing
by 0.9, and they would return exact results in the smaller number of
cases where gradian results are exactly representable.

... but having said that, your argument here is faulty, because 0.9
in itself is not exactly representable in binary. You'd be relying
on roundoff happening in the right direction to get exact answers
from such calculations, for just the same reasons that sind() can't be
just "sin(x * scalefactor)" if you want exact-where-possible results.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#8Simon Riggs
simon@2ndQuadrant.com
In reply to: Tom Lane (#7)
Re: Proposal: Trigonometric functions in degrees

On 26 October 2015 at 10:18, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Dean Rasheed <dean.a.rasheed@gmail.com> writes:

On 25 October 2015 at 09:16, Emre Hasegeli <emre@hasegeli.com> wrote:

I would prefer gradian over degree.

I think gradians are generally less commonly used than degrees and
radians, so I'm less inclined to include them.

I agree. gradians are not often used at all, AFAICT.

I've never seen anyone use a gradian, even though my calculator had them
when I was 16.

I even misread the request, thinking he meant "radians". Definitely -1 to
gradians in PostgreSQL.

Also -1 to furlongs, fortnights, pecks and hundredweight, amongst others.

--
Simon Riggs http://www.2ndQuadrant.com/
<http://www.2ndquadrant.com/&gt;
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#9Robert Haas
robertmhaas@gmail.com
In reply to: Simon Riggs (#8)
Re: Proposal: Trigonometric functions in degrees

On Mon, Oct 26, 2015 at 1:29 PM, Simon Riggs <simon@2ndquadrant.com> wrote:

On 26 October 2015 at 10:18, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Dean Rasheed <dean.a.rasheed@gmail.com> writes:

On 25 October 2015 at 09:16, Emre Hasegeli <emre@hasegeli.com> wrote:

I would prefer gradian over degree.

I think gradians are generally less commonly used than degrees and
radians, so I'm less inclined to include them.

I agree. gradians are not often used at all, AFAICT.

I've never seen anyone use a gradian, even though my calculator had them
when I was 16.

I even misread the request, thinking he meant "radians". Definitely -1 to
gradians in PostgreSQL.

Also -1 to furlongs, fortnights, pecks and hundredweight, amongst others.

Aw, you're no fun. select '1 fortnight'::interval => '14 days' would be cool.

https://en.wikipedia.org/wiki/FFF_system

I don't think we should be dismissive of gradians, because I'm sure
Emre's request was serious and in good faith, but I don't feel a
crying need for them either.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#10Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Tom Lane (#7)
Re: Proposal: Trigonometric functions in degrees

On 26 October 2015 at 14:18, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Having degree-based functions would make it trivial to implement
user-defined gradian-based functions, just by multiplying or dividing
by 0.9, and they would return exact results in the smaller number of
cases where gradian results are exactly representable.

... but having said that, your argument here is faulty, because 0.9
in itself is not exactly representable in binary. You'd be relying
on roundoff happening in the right direction to get exact answers
from such calculations, for just the same reasons that sind() can't be
just "sin(x * scalefactor)" if you want exact-where-possible results.

It would be relying on the things like the following being exactly true:

45 / 0.9 = 50
50 * 0.9 = 45
90 / 0.9 = 100
100 * 0.9 = 90

which they are on my machine. I thought that this was guaranteed in
IEEE floating point arithmetic, since one of the inputs and the result
are exactly representable, and the result is guaranteed to be within
0.5ulp. I'm curious now though. Are there any platforms on which the
above aren't exactly true?

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#11Simon Riggs
simon@2ndQuadrant.com
In reply to: Robert Haas (#9)
Re: Proposal: Trigonometric functions in degrees

On 26 October 2015 at 13:58, Robert Haas <robertmhaas@gmail.com> wrote:

Also -1 to furlongs, fortnights, pecks and hundredweight, amongst others.

Aw, you're no fun. select '1 fortnight'::interval => '14 days' would be
cool.

Deviation from SI units is no laughing matter. How would we even know how
to capitalise "Fortnight"?

--
Simon Riggs http://www.2ndQuadrant.com/
<http://www.2ndquadrant.com/&gt;
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#12Tom Lane
tgl@sss.pgh.pa.us
In reply to: Dean Rasheed (#10)
Re: Proposal: Trigonometric functions in degrees

Dean Rasheed <dean.a.rasheed@gmail.com> writes:

On 26 October 2015 at 14:18, Tom Lane <tgl@sss.pgh.pa.us> wrote:

... but having said that, your argument here is faulty, because 0.9
in itself is not exactly representable in binary. You'd be relying
on roundoff happening in the right direction to get exact answers
from such calculations, for just the same reasons that sind() can't be
just "sin(x * scalefactor)" if you want exact-where-possible results.

It would be relying on the things like the following being exactly true:

45 / 0.9 = 50
50 * 0.9 = 45
90 / 0.9 = 100
100 * 0.9 = 90

which they are on my machine. I thought that this was guaranteed in
IEEE floating point arithmetic, since one of the inputs and the result
are exactly representable, and the result is guaranteed to be within
0.5ulp. I'm curious now though. Are there any platforms on which the
above aren't exactly true?

Possibly, but the bigger picture is you're ignoring other cases, such as

regression=# select 30 / 0.9;
?column?
---------------------
33.3333333333333333
(1 row)

which is problematic since sin(30 degrees) = 0.5 is one of the cases
one would like to be exact. (Actually I guess this example shows that
implementing sind in terms of sing would be imprecise, but I'm sure
the reverse holds for some other special angles.)

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#13Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Tom Lane (#12)
Re: Proposal: Trigonometric functions in degrees

On 26 October 2015 at 18:58, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Dean Rasheed <dean.a.rasheed@gmail.com> writes:

On 26 October 2015 at 14:18, Tom Lane <tgl@sss.pgh.pa.us> wrote:

... but having said that, your argument here is faulty, because 0.9
in itself is not exactly representable in binary. You'd be relying
on roundoff happening in the right direction to get exact answers
from such calculations, for just the same reasons that sind() can't be
just "sin(x * scalefactor)" if you want exact-where-possible results.

It would be relying on the things like the following being exactly true:

45 / 0.9 = 50
50 * 0.9 = 45
90 / 0.9 = 100
100 * 0.9 = 90

which they are on my machine. I thought that this was guaranteed in
IEEE floating point arithmetic, since one of the inputs and the result
are exactly representable, and the result is guaranteed to be within
0.5ulp. I'm curious now though. Are there any platforms on which the
above aren't exactly true?

Possibly, but the bigger picture is you're ignoring other cases, such as

regression=# select 30 / 0.9;
?column?
---------------------
33.3333333333333333
(1 row)

which is problematic since sin(30 degrees) = 0.5 is one of the cases
one would like to be exact. (Actually I guess this example shows that
implementing sind in terms of sing would be imprecise, but I'm sure
the reverse holds for some other special angles.)

No, actually sing() and the other gradian-based trig functions have
fewer exact special cases than their degree-based equivalents. That's
one of the criticisms of gradians. For example an equilateral triangle
has internal angles of 66.666... gradians. The only exact results for
sing() and cosg() are at multiples of 100 gradians.

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#14Peter Eisentraut
peter_e@gmx.net
In reply to: Dean Rasheed (#1)
Re: Proposal: Trigonometric functions in degrees

On 10/24/15 5:24 AM, Dean Rasheed wrote:

Additionally, functions that worked natively in degrees would be able
to return exact answers in special cases like cosd(90) = 0, whereas
cos(radians(90)) is not exactly 0 because pi/2 cannot be represented
exactly as a floating point number.

But how you are going to implement that? I don't see a sind() in the C
library.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#15Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Peter Eisentraut (#14)
Re: Proposal: Trigonometric functions in degrees

On 26 October 2015 at 19:45, Peter Eisentraut <peter_e@gmx.net> wrote:

On 10/24/15 5:24 AM, Dean Rasheed wrote:

Additionally, functions that worked natively in degrees would be able
to return exact answers in special cases like cosd(90) = 0, whereas
cos(radians(90)) is not exactly 0 because pi/2 cannot be represented
exactly as a floating point number.

But how you are going to implement that? I don't see a sind() in the C
library.

I'm thinking something along the lines of:

1. Reduce the range of the input (say to 0..90 degrees).
2. Handle special cases (0, 30 and 90 for sind()).
3. Otherwise convert to radians for the general case.

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#16Tom Lane
tgl@sss.pgh.pa.us
In reply to: Dean Rasheed (#15)
Re: Proposal: Trigonometric functions in degrees

Dean Rasheed <dean.a.rasheed@gmail.com> writes:

On 26 October 2015 at 19:45, Peter Eisentraut <peter_e@gmx.net> wrote:

But how you are going to implement that? I don't see a sind() in the C
library.

I'm thinking something along the lines of:

1. Reduce the range of the input (say to 0..90 degrees).
2. Handle special cases (0, 30 and 90 for sind()).
3. Otherwise convert to radians for the general case.

I'd be okay with #1 and #3, but #2 is just a crock; it would risk creating
problems that did not exist before, such as non-monotonicity of the
function result (over ranges where that does not hold).

I looked into my dusty old copy of Cody & Waite's "Software Manual for the
Elementary Functions", which is what I used as a reference the last time
I had to do code like this (which admittedly was quite a long time ago;
the state of the art might've advanced). C&W say that the key accuracy
limit for sin and cos is reduction of the argument modulo pi (or whichever
multiple of pi you choose to work with). Now that problem just goes away
for degrees, of course, so it might be that reduction mod 360 and then
conversion to radians would be Good Enough(TM).

If it's not good enough, a possible idea is reduction mod 45 degrees or
even mod 30 degrees and then using trig identities to reconstruct the
correct output.

Anyway, I think the core idea of trying to build a reasonably thin wrapper
around sin(3m) and friends is probably an appropriate amount of effort,
depending on how many cases you are hoping to make exact. I doubt it's
worth coding sind() from scratch.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#17Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Tom Lane (#16)
Re: Proposal: Trigonometric functions in degrees

On 26 October 2015 at 20:19, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Dean Rasheed <dean.a.rasheed@gmail.com> writes:

I'm thinking something along the lines of:

1. Reduce the range of the input (say to 0..90 degrees).
2. Handle special cases (0, 30 and 90 for sind()).
3. Otherwise convert to radians for the general case.

I'd be okay with #1 and #3, but #2 is just a crock; it would risk creating
problems that did not exist before, such as non-monotonicity of the
function result (over ranges where that does not hold).

Well special-casing 0 and 90 are certainly no problem, and do not risk
introducing non-monotonicity. sin() changes sign at 0 and for very
small x, sin(x) ~= x, so special-casing sin(0) = 0, doesn't affect the
monotonic nature of the function in that vicinity. Near 90, sin(x) is
not monotonic, but rather that is a maxima, and the slope is 0, so
small perturbations in x do not affect the result and you have to move
quite far away from 90 for the result to become different from 1, so
special-casing sind(90) = 1 does not affect the continuity of the
function.

Special-casing sind(30) = 0.5 could be more problematic, but a quick
test on my machine shows that it does remain monotonic. As I
understand it, most modern processors apply argument reduction to the
range 0..pi/4, using cos(pi/2-x)=sin(x) to extend this to the range
0..pi/2 and hence all inputs. So passing in an input close to pi/6 (30
degrees) will not undergo any further argument reduction and the
result would be expected to be very accurate. Of course, we could just
omit this special case, and accept whatever approximation to 0.5 the
underlying function returned. It's a question of which is worse in
practice -- a small chance that sind(29.999...) or sind(30.000...1)
might be the wrong side of 0.5, making the function non-monotonic, or
having sind(30) not be exactly 0.5.

Personally I'd rather have sind(30) be exactly 0.5, even if
sind(29.999...) or sind(30.000...1) ended up the wrong side of it.
Floating point arithmetic is inherently imprecise, so tiny errors are
to be expected. But the well-known exact cases ought to work exactly.
BTW, these are the *only* values for which sind() returns exact
answers (see Niven's Theorum -
http://mathworld.wolfram.com/NivensTheorem.html) and they also happen
to be exactly representable using floats, so it seems a shame not to
return the exact values whenever possible.

I looked into my dusty old copy of Cody & Waite's "Software Manual for the
Elementary Functions", which is what I used as a reference the last time
I had to do code like this (which admittedly was quite a long time ago;
the state of the art might've advanced). C&W say that the key accuracy
limit for sin and cos is reduction of the argument modulo pi (or whichever
multiple of pi you choose to work with). Now that problem just goes away
for degrees, of course, so it might be that reduction mod 360 and then
conversion to radians would be Good Enough(TM).

No that would lead to sind(180) being non-zero.

If it's not good enough, a possible idea is reduction mod 45 degrees or
even mod 30 degrees and then using trig identities to reconstruct the
correct output.

Reduction mod 45 would be fairly trivial, and would result in things
like sind(10) being exactly equal to cosd(80), even though neither
answer would be exactly correct. I'm not sure that by itself is worth
the extra effort, but it would probably improve the chances of cosd()
staying monotonic near 60, if we special-cased cosd(60) = 0.5
(although it does anyway on my machine).

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#18Bear Giles
bgiles@coyotesong.com
In reply to: Dean Rasheed (#17)
Re: Proposal: Trigonometric functions in degrees

Stupid question - is sin(3m) a call-through to the math coprocessor?​ It
probably only matters when doing a series of calculations (where the extra
guard bits can matter) and not when doing a simple one-time lookup but it
might be something to consider in regards to setting a precedent.

Bear

#19Jim Nasby
Jim.Nasby@BlueTreble.com
In reply to: Simon Riggs (#11)
Re: Proposal: Trigonometric functions in degrees

On 10/26/15 1:48 PM, Simon Riggs wrote:

Deviation from SI units is no laughing matter. How would we even know
how to capitalise "Fortnight"?

It's all fun and games until someone pokes Mars' eye out with a spacecraft.

https://en.wikipedia.org/wiki/Mars_Climate_Orbiter

(Ok, I guess we just sprayed the planet with debris instead of poking an
eye out...)
--
Jim Nasby, Data Architect, Blue Treble Consulting, Austin TX
Experts in Analytics, Data Architecture and PostgreSQL
Data in Trouble? Get it in Treble! http://BlueTreble.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#20Tom Lane
tgl@sss.pgh.pa.us
In reply to: Dean Rasheed (#17)
Re: Proposal: Trigonometric functions in degrees

Dean Rasheed <dean.a.rasheed@gmail.com> writes:

Personally I'd rather have sind(30) be exactly 0.5, even if
sind(29.999...) or sind(30.000...1) ended up the wrong side of it.

TBH, sir, if you think that you are too dangerous to be allowed anywhere
near any numerical analysis code. Preserving mathematical properties like
monotonicity is *far* more important than whether sin(30 degrees) comes
out exact or not. You can do proofs about the behavior of algorithms
using these functions if you can ensure monotonicity; but exactness of
values is always going to depend on subtle details of the floating point
format.

I think using a range reduction to some fractional part of the circle is a
reasonable way of trying to deal with these concerns, but I really really
do not want to see it special-casing point values in a way that doesn't
guarantee monotonicity.

It may be that guaranteeing the sin(30) case is just not very feasible,
and we should content ourselves with getting the 0/90/180/270/360 cases
exactly, which we could do with a mod 90 initial reduction. Doing mod 30
or mod 45 would require sewing together pieces of the curve that might not
meet exactly, if we were unlucky. (I've not tried it though.)

What have you got in mind for tan() and the rest?

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#21Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Tom Lane (#20)
Re: Proposal: Trigonometric functions in degrees

On 26 October 2015 at 22:51, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Dean Rasheed <dean.a.rasheed@gmail.com> writes:

Personally I'd rather have sind(30) be exactly 0.5, even if
sind(29.999...) or sind(30.000...1) ended up the wrong side of it.

TBH, sir, if you think that you are too dangerous to be allowed anywhere
near any numerical analysis code. Preserving mathematical properties like
monotonicity is *far* more important than whether sin(30 degrees) comes
out exact or not. You can do proofs about the behavior of algorithms
using these functions if you can ensure monotonicity; but exactness of
values is always going to depend on subtle details of the floating point
format.

I think using a range reduction to some fractional part of the circle is a
reasonable way of trying to deal with these concerns, but I really really
do not want to see it special-casing point values in a way that doesn't
guarantee monotonicity.

OK, agreed. Preserving the monotonicity of sind over the range 0..90
is important. It's useful having this discussion before implementing a
lot of code that people might come to rely on.

It may be that guaranteeing the sin(30) case is just not very feasible,
and we should content ourselves with getting the 0/90/180/270/360 cases
exactly, which we could do with a mod 90 initial reduction. Doing mod 30
or mod 45 would require sewing together pieces of the curve that might not
meet exactly, if we were unlucky. (I've not tried it though.)

I think it's still feasible to have sind(30) = 0.5 exactly and keep
monotonicity. If we can implement sind over the range 0..90, the rest
is easy, and in that range there are 3 known fixed points that we want
to preserve -- (0,0), (30,0.5) and (90,1). So all we need is a way to
compute each half of the range, interpolating between the fixed
points. That ought to be possible by pre-computing the value of
sin(pi/6) and using it to scale the result of sin(x) to guarantee that
the pieces join in the middle. (I've not tried it either, but it
sounds plausible.)

What have you got in mind for tan() and the rest?

If we have sind() for the range 0..90, cosd() could be implemented as
sind(90 - x) after reducing x to the range 0..90, which would then
give it the right set of properties and it would be exact for 0, 60
and 90.

Then tand() and cotd() could be implemented as their ratios, which
would guarantee that tand(45) = cotd(45) = 1 exactly and that they are
monotonic nearby, without needing any special case code.

I haven't thought about the inverse functions yet, but they ought to
be possible in a similar way, albeit a bit fiddly.

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#22Nathan Wagner
nw+pg@hydaspes.if.org
In reply to: Robert Haas (#9)
1 attachment(s)
fortnight interval support

On Mon, Oct 26, 2015 at 01:58:52PM -0400, Robert Haas wrote:

Aw, you're no fun. select '1 fortnight'::interval => '14 days' would be cool.

Patch attached...

:)

% psql -p 5433 -d template1 -h localhost
psql (9.4.5, server 9.6devel)
WARNING: psql major version 9.4, server major version 9.6.
Some psql features might not work.
Type "help" for help.

template1=# select current_date;
date
------------
2015-10-27
(1 row)

template1=# select '1 fortnight'::interval;
interval
----------
14 days
(1 row)

template1=# select current_date + '1 fortnight'::interval;
?column?
---------------------
2015-11-10 00:00:00
(1 row)

template1=# select current_date + '1.3 fortnight'::interval;
?column?
---------------------
2015-11-14 04:48:00
(1 row)

template1=# select current_date + '1.3 fortnights'::interval;
?column?
---------------------
2015-11-14 04:48:00
(1 row)

--
nw

Attachments:

fortnight.patchtext/plain; charset=us-asciiDownload
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 926358e..2032fe0 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -186,6 +186,8 @@ static const datetkn deltatktbl[] = {
 	{DDECADE, UNITS, DTK_DECADE},		/* "decade" relative */
 	{"decades", UNITS, DTK_DECADE},		/* "decades" relative */
 	{"decs", UNITS, DTK_DECADE},	/* "decades" relative */
+	{DFORTNIGHT, UNITS, DTK_FORTNIGHT}, /* "fortnights" relative */
+	{"fortnights", UNITS, DTK_FORTNIGHT}, /* "fortnights" relative */
 	{"h", UNITS, DTK_HOUR},		/* "hour" relative */
 	{DHOUR, UNITS, DTK_HOUR},	/* "hour" relative */
 	{"hours", UNITS, DTK_HOUR}, /* "hours" relative */
@@ -3281,6 +3283,12 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 						tmask = DTK_M(DAY);
 						break;
 
+					case DTK_FORTNIGHT:
+						tm->tm_mday += val * 14;
+						AdjustFractDays(fval, tm, fsec, 14);
+						tmask = DTK_M(WEEK);
+						break;
+
 					case DTK_WEEK:
 						tm->tm_mday += val * 7;
 						AdjustFractDays(fval, tm, fsec, 7);
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index e9a1ece..3641292 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -52,6 +52,7 @@ struct tzEntry;
 #define DHOUR			"hour"
 #define DDAY			"day"
 #define DWEEK			"week"
+#define DFORTNIGHT		"fortnight"
 #define DMONTH			"month"
 #define DQUARTER		"quarter"
 #define DYEAR			"year"
@@ -181,6 +182,7 @@ struct tzEntry;
 #define DTK_TZ_MINUTE	35
 #define DTK_ISOYEAR		36
 #define DTK_ISODOW		37
+#define DTK_FORTNIGHT		38
 
 
 /*
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index c873a99..7a72f2a 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -40,6 +40,12 @@ SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
  10 days 12:00:00
 (1 row)
 
+SELECT INTERVAL '1 fortnight' AS "Fourteen days";
+ Fourteen days 
+---------------
+ 14 days
+(1 row)
+
 SELECT INTERVAL '1.5 months' AS "One month 15 days";
  One month 15 days 
 -------------------
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 789c3de..285266a 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -12,6 +12,7 @@ SELECT INTERVAL '-08:00' AS "Eight hours";
 SELECT INTERVAL '-1 +02:03' AS "22 hours ago...";
 SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
 SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
+SELECT INTERVAL '1 fortnight' AS "Fourteen days";
 SELECT INTERVAL '1.5 months' AS "One month 15 days";
 SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
 
#23Merlin Moncure
mmoncure@gmail.com
In reply to: Nathan Wagner (#22)
Re: fortnight interval support

On Tue, Oct 27, 2015 at 8:52 AM, Nathan Wagner <nw+pg@hydaspes.if.org> wrote:

On Mon, Oct 26, 2015 at 01:58:52PM -0400, Robert Haas wrote:

Aw, you're no fun. select '1 fortnight'::interval => '14 days' would be cool.

Patch attached...

:)

This is very cool (you are 100% certain there are no performance
impacts on current cases, right?)! :-)

merlin

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#24David Fetter
david@fetter.org
In reply to: Nathan Wagner (#22)
Re: fortnight interval support

On Tue, Oct 27, 2015 at 01:52:11PM +0000, Nathan Wagner wrote:

On Mon, Oct 26, 2015 at 01:58:52PM -0400, Robert Haas wrote:

Aw, you're no fun. select '1 fortnight'::interval => '14 days'
would be cool.

Patch attached...

:)

I'd argue for a back-patch all the way to 9.1, as the lack of
fortnight support is clearly a bug.

Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#25Gavin Flower
GavinFlower@archidevsys.co.nz
In reply to: Nathan Wagner (#22)
Re: fortnight interval support

On 28/10/15 02:52, Nathan Wagner wrote:

On Mon, Oct 26, 2015 at 01:58:52PM -0400, Robert Haas wrote:

Aw, you're no fun. select '1 fortnight'::interval => '14 days' would be cool.

Patch attached...

:)

[...]

You trying to get PostgreSQL banned in France??? :-)

When I was learning French many years ago, I was told that the French
consider their fortnight to be 15 days!!!

see:
https://nz.answers.yahoo.com/question/index?qid=20100920093722AAc54Xo
https://en.wikipedia.org/wiki/Fortnight
http://www.wordsense.eu/fortnight

Cheers,
Gavin

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#26Nathan Wagner
nw+pg@hydaspes.if.org
In reply to: Gavin Flower (#25)
Re: fortnight interval support

On Wed, Oct 28, 2015 at 08:17:25AM +1300, Gavin Flower wrote:

You trying to get PostgreSQL banned in France??? :-)

When I was learning French many years ago, I was told that the French
consider their fortnight to be 15 days!!!

What, it's a "fortnight", not a "quinzaine".

You have no idea how hard it was to resist updating the patch...

--
nw

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#27Nathan Wagner
nw+pg@hydaspes.if.org
In reply to: Merlin Moncure (#23)
Re: fortnight interval support

On Tue, Oct 27, 2015 at 12:04:55PM -0500, Merlin Moncure wrote:

On Tue, Oct 27, 2015 at 8:52 AM, Nathan Wagner <nw+pg@hydaspes.if.org> wrote:

On Mon, Oct 26, 2015 at 01:58:52PM -0400, Robert Haas wrote:

Aw, you're no fun. select '1 fortnight'::interval => '14 days' would be cool.

Patch attached...

This is very cool (you are 100% certain there are no performance
impacts on current cases, right?)! :-)

It passed the regression test. It must be perfect :)

--
nw

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#28Nathan Wagner
nw+pg@hydaspes.if.org
In reply to: Nathan Wagner (#22)
Re: fortnight interval support

On Tue, Oct 27, 2015 at 01:52:11PM +0000, Nathan Wagner wrote:

On Mon, Oct 26, 2015 at 01:58:52PM -0400, Robert Haas wrote:

Aw, you're no fun. select '1 fortnight'::interval => '14 days' would be cool.

Patch attached...

This isn't necessarily bad, but I observe that it would be difficult or
impossible to add fortnight support to intervals as an extension rather
than by modifying the scanner tables that the interval parser uses at
original compile time. You might be able to override symbols with a C
extension, but parts of the parser are static (in the C sense), so you'd
need to override and duplicate a lot of the existing functions. Of
course, a hookable interval parser is absurd in the first place.

--
nw

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#29Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Dean Rasheed (#21)
1 attachment(s)
Re: Proposal: Trigonometric functions in degrees

On 27 October 2015 at 08:24, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

I think it's still feasible to have sind(30) = 0.5 exactly and keep
monotonicity....

Here's a patch along those lines. It turned out to be fairly
straightforward. It's still basically a thin wrapper on top of the
math library trig functions, with a bit of careful scaling to ensure
that curves join together to form continuous functions that are
monotonic in the expected regions and return exact values in all the
special cases 0,30,45,60,90,...

I also modified some of the CHECKFLOATVAL() checks which didn't look
right to me, unless there's some odd platform-specific behaviour that
I'm not aware of, functions like sin and asin should never return
infinity.

Regards,
Dean

Attachments:

trigd.patchtext/x-diff; charset=US-ASCII; name=trigd.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
new file mode 100644
index 2946122..434fb88
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -993,9 +993,9 @@
    Finally, <xref linkend="functions-math-trig-table"> shows the
    available trigonometric functions.  All trigonometric functions
    take arguments and return values of type <type>double
-   precision</type>. Trigonometric functions arguments are expressed
-   in radians. Inverse functions return values are expressed in
-   radians.  See unit transformation functions
+   precision</type>.  Each of the trigonometric functions comes in
+   two varieties, one which works in radians and one which works in
+   degrees.  See unit transformation functions
    <literal><function>radians()</function></literal> and
    <literal><function>degrees()</function></literal> above.
   </para>
@@ -1003,10 +1003,11 @@
    <table id="functions-math-trig-table">
     <title>Trigonometric Functions</title>
 
-    <tgroup cols="2">
+    <tgroup cols="3">
      <thead>
       <row>
-       <entry>Function</entry>
+       <entry>Function (radians)</entry>
+       <entry>Function (degrees)</entry>
        <entry>Description</entry>
       </row>
      </thead>
@@ -1018,6 +1019,11 @@
          <primary>acos</primary>
         </indexterm><literal><function>acos(<replaceable>x</replaceable>)</function></literal>
        </entry>
+       <entry>
+        <indexterm>
+         <primary>acosd</primary>
+        </indexterm><literal><function>acosd(<replaceable>x</replaceable>)</function></literal>
+       </entry>
        <entry>inverse cosine</entry>
       </row>
 
@@ -1028,6 +1034,12 @@
         </indexterm>
         <literal><function>asin(<replaceable>x</replaceable>)</function></literal>
        </entry>
+       <entry>
+        <indexterm>
+         <primary>asind</primary>
+        </indexterm>
+        <literal><function>asind(<replaceable>x</replaceable>)</function></literal>
+       </entry>
        <entry>inverse sine</entry>
       </row>
 
@@ -1038,6 +1050,12 @@
         </indexterm>
         <literal><function>atan(<replaceable>x</replaceable>)</function></literal>
        </entry>
+       <entry>
+        <indexterm>
+         <primary>atand</primary>
+        </indexterm>
+        <literal><function>atand(<replaceable>x</replaceable>)</function></literal>
+       </entry>
        <entry>inverse tangent</entry>
       </row>
 
@@ -1049,6 +1067,13 @@
         <literal><function>atan2(<replaceable>y</replaceable>,
         <replaceable>x</replaceable>)</function></literal>
        </entry>
+       <entry>
+        <indexterm>
+         <primary>atan2d</primary>
+        </indexterm>
+        <literal><function>atan2d(<replaceable>y</replaceable>,
+        <replaceable>x</replaceable>)</function></literal>
+       </entry>
        <entry>inverse tangent of
         <literal><replaceable>y</replaceable>/<replaceable>x</replaceable></literal></entry>
       </row>
@@ -1060,6 +1085,12 @@
         </indexterm>
         <literal><function>cos(<replaceable>x</replaceable>)</function></literal>
        </entry>
+       <entry>
+        <indexterm>
+         <primary>cosd</primary>
+        </indexterm>
+        <literal><function>cosd(<replaceable>x</replaceable>)</function></literal>
+       </entry>
        <entry>cosine</entry>
       </row>
 
@@ -1070,6 +1101,12 @@
         </indexterm>
         <literal><function>cot(<replaceable>x</replaceable>)</function></literal>
        </entry>
+       <entry>
+        <indexterm>
+         <primary>cotd</primary>
+        </indexterm>
+        <literal><function>cotd(<replaceable>x</replaceable>)</function></literal>
+       </entry>
        <entry>cotangent</entry>
       </row>
 
@@ -1080,6 +1117,12 @@
         </indexterm>
         <literal><function>sin(<replaceable>x</replaceable>)</function></literal>
        </entry>
+       <entry>
+        <indexterm>
+         <primary>sind</primary>
+        </indexterm>
+        <literal><function>sind(<replaceable>x</replaceable>)</function></literal>
+       </entry>
        <entry>sine</entry>
       </row>
 
@@ -1090,6 +1133,12 @@
         </indexterm>
         <literal><function>tan(<replaceable>x</replaceable>)</function></literal>
        </entry>
+       <entry>
+        <indexterm>
+         <primary>tand</primary>
+        </indexterm>
+        <literal><function>tand(<replaceable>x</replaceable>)</function></literal>
+       </entry>
        <entry>tangent</entry>
       </row>
      </tbody>
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
new file mode 100644
index 4e927d8..d2318f7
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -1535,7 +1535,7 @@ dacos(PG_FUNCTION_ARGS)
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("input is out of range")));
 
-	CHECKFLOATVAL(result, isinf(arg1), true);
+	CHECKFLOATVAL(result, false, true);
 	PG_RETURN_FLOAT8(result);
 }
 
@@ -1556,7 +1556,7 @@ dasin(PG_FUNCTION_ARGS)
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("input is out of range")));
 
-	CHECKFLOATVAL(result, isinf(arg1), true);
+	CHECKFLOATVAL(result, false, true);
 	PG_RETURN_FLOAT8(result);
 }
 
@@ -1577,7 +1577,7 @@ datan(PG_FUNCTION_ARGS)
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("input is out of range")));
 
-	CHECKFLOATVAL(result, isinf(arg1), true);
+	CHECKFLOATVAL(result, false, true);
 	PG_RETURN_FLOAT8(result);
 }
 
@@ -1599,7 +1599,7 @@ datan2(PG_FUNCTION_ARGS)
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("input is out of range")));
 
-	CHECKFLOATVAL(result, isinf(arg1) || isinf(arg2), true);
+	CHECKFLOATVAL(result, false, true);
 	PG_RETURN_FLOAT8(result);
 }
 
@@ -1620,7 +1620,7 @@ dcos(PG_FUNCTION_ARGS)
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("input is out of range")));
 
-	CHECKFLOATVAL(result, isinf(arg1), true);
+	CHECKFLOATVAL(result, false, true);
 	PG_RETURN_FLOAT8(result);
 }
 
@@ -1642,7 +1642,7 @@ dcot(PG_FUNCTION_ARGS)
 				 errmsg("input is out of range")));
 
 	result = 1.0 / result;
-	CHECKFLOATVAL(result, true /* cotan(pi/2) == inf */ , true);
+	CHECKFLOATVAL(result, true /* cot(0) == Inf */ , true);
 	PG_RETURN_FLOAT8(result);
 }
 
@@ -1663,7 +1663,7 @@ dsin(PG_FUNCTION_ARGS)
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("input is out of range")));
 
-	CHECKFLOATVAL(result, isinf(arg1), true);
+	CHECKFLOATVAL(result, false, true);
 	PG_RETURN_FLOAT8(result);
 }
 
@@ -1688,6 +1688,368 @@ dtan(PG_FUNCTION_ARGS)
 	PG_RETURN_FLOAT8(result);
 }
 
+
+/*
+ *		asind_q1		- returns the inverse sine of x in degrees, where x is
+ *						  assumed to be in the range [0, 1] and the result is
+ *						  an angle in the first quadrant (0 to 90 degrees).
+ *
+ * In this quadrant there are 3 special case inputs (0, 0.5 and 1) for which
+ * this function will return exact values (0, 30 and 90 respectively).
+ */
+static double
+asind_q1(double x)
+{
+	/*
+	 * Stitch together inverse sine and cosine functions for the ranges
+	 * [0, 0.5] and [0.5, 1].  Each expression below is guaranteed to return
+	 * exactly 30 for x=0.5, so the result is a continuous monotonic function
+	 * over the full range.
+	 */
+	if (x <= 0.5)
+		return (asin(x) / asin(0.5)) * 30;
+	else
+		return 90 - (acos(x) / acos(0.5)) * 60;
+}
+
+
+/*
+ *		acosd_q1		- returns the inverse cosine of x in degrees, where x
+ *						  is assumed to be in the range [0, 1] and the result
+ *						  is an angle in the first quadrant (0 to 90 degrees).
+ *
+ * In this quadrant there are 3 special case inputs (0, 0.5 and 1) for which
+ * this function will return exact values (0, 60 and 90 respectively).
+ */
+static double
+acosd_q1(double x)
+{
+	/*
+	 * Stitch together inverse sine and cosine functions for the ranges
+	 * [0, 0.5] and [0.5, 1].  Each expression below is guaranteed to return
+	 * exactly 60 for x=0.5, so the result is a continuous monotonic function
+	 * over the full range.
+	 */
+	if (x <= 0.5)
+		return 90 - (asin(x) / asin(0.5)) * 30;
+	else
+		return (acos(x) / acos(0.5)) * 60;
+}
+
+
+/*
+ *		dacosd			- returns the arccos of arg1 (degrees)
+ */
+Datum
+dacosd(PG_FUNCTION_ARGS)
+{
+	float8		arg1 = PG_GETARG_FLOAT8(0);
+	float8		result;
+
+	if (isinf(arg1) || arg1 < -1 || arg1 > 1)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("input is out of range")));
+
+	if (isnan(arg1))
+		result = arg1;
+	else if (arg1 >= 0)
+		result = acosd_q1(arg1);
+	else
+		result = 90 + asind_q1(-arg1);
+
+	CHECKFLOATVAL(result, false, true);
+	PG_RETURN_FLOAT8(result);
+}
+
+
+/*
+ *		dasind			- returns the arcsin of arg1 (degrees)
+ */
+Datum
+dasind(PG_FUNCTION_ARGS)
+{
+	float8		arg1 = PG_GETARG_FLOAT8(0);
+	float8		result;
+
+	if (isinf(arg1) || arg1 < -1 || arg1 > 1)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("input is out of range")));
+
+	if (isnan(arg1))
+		result = arg1;
+	else if (arg1 >= 0)
+		result = asind_q1(arg1);
+	else
+		result = -asind_q1(-arg1);
+
+	CHECKFLOATVAL(result, false, true);
+	PG_RETURN_FLOAT8(result);
+}
+
+
+/*
+ *		datand			- returns the arctan of arg1 (degrees)
+ */
+Datum
+datand(PG_FUNCTION_ARGS)
+{
+	float8		arg1 = PG_GETARG_FLOAT8(0);
+	float8		result;
+
+	errno = 0;
+	result = atan(arg1);
+	if (errno != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("input is out of range")));
+
+	result = (result / atan(1.0)) * 45;
+	CHECKFLOATVAL(result, false, true);
+	PG_RETURN_FLOAT8(result);
+}
+
+
+/*
+ *		atan2d			- returns the arctan2 of arg1 (degrees)
+ */
+Datum
+datan2d(PG_FUNCTION_ARGS)
+{
+	float8		arg1 = PG_GETARG_FLOAT8(0);
+	float8		arg2 = PG_GETARG_FLOAT8(1);
+	float8		result;
+
+	errno = 0;
+	result = atan2(arg1, arg2);
+	if (errno != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("input is out of range")));
+
+	result = (result / atan(1.0)) * 45;
+	CHECKFLOATVAL(result, false, true);
+	PG_RETURN_FLOAT8(result);
+}
+
+
+/*
+ *		sind_0_to_30	- returns the sine of an angle that lies between 0 and
+ *						  30 degrees.  This will return exactly 0 when x is 0,
+ *						  and exactly 0.5 when x is 30 degrees.
+ */
+static double
+sind_0_to_30(double x)
+{
+	return ( sin(x * (M_PI/180)) / sin(30 * (M_PI/180)) ) / 2;
+}
+
+
+/*
+ *		cosd_0_to_60	- returns the cosine of an angle that lies between 0
+ *						  and 60 degrees.  This will return exactly 1 when x
+ *						  is 0 and exactly 0.5 when x is 60 degrees.
+ */
+static double
+cosd_0_to_60(double x)
+{
+	return ( 2 - (1 - cos(x * (M_PI/180))) / (1 - cos(60 * (M_PI/180))) ) / 2;
+}
+
+
+/*
+ *		sind_q1			- returns the sine of an angle in the first quadrant
+ *						  (0 to 90 degrees).
+ */
+static double
+sind_q1(double x)
+{
+	/*
+	 * Stitch together the sine and cosine functions for the ranges [0, 30]
+	 * and [30, 90].  These guarantee to return exact answers at their
+	 * endpoints, so the overall result is a continuous monotonic function
+	 * that gives exact results when x = 0, 30 and 90 degrees.
+	 */
+	return x <= 30 ? sind_0_to_30(x) : cosd_0_to_60(90-x);
+}
+
+
+/*
+ *		cosd_q1			- returns the cosine of an angle in the first quadrant
+ *						  (0 to 90 degrees).
+ */
+static double
+cosd_q1(double x)
+{
+	/*
+	 * Stitch together the sine and cosine functions for the ranges [0, 60]
+	 * and [60, 90].  These guarantee to return exact answers at their
+	 * endpoints, so the overall result is a continuous monotonic function
+	 * that gives exact results when x = 0, 60 and 90 degrees.
+	 */
+	return x <= 60 ? cosd_0_to_60(x) : sind_0_to_30(90-x);
+}
+
+
+/*
+ *		dcosd			- returns the cosine of arg1 (degrees)
+ */
+Datum
+dcosd(PG_FUNCTION_ARGS)
+{
+	float8		arg1 = PG_GETARG_FLOAT8(0);
+	int			sign = 1;
+	float8		result;
+
+	/* Reduce the range of the input to [0,90] degrees */
+	arg1 = fmod(arg1, 360.0);
+	if (isnan(arg1))
+		PG_RETURN_FLOAT8(arg1);
+
+	if (arg1 < 0)
+		/* cosd(-x) = cosd(x) */
+		arg1 = -arg1;
+
+	if (arg1 > 180)
+		/* cosd(360-x) = cosd(x) */
+		arg1 = 360 - arg1;
+
+	if (arg1 > 90)
+	{
+		/* cosd(180-x) = -cosd(x) */
+		arg1 = 180 - arg1;
+		sign = -sign;
+	}
+
+	result = sign * cosd_q1(arg1);
+	CHECKFLOATVAL(result, false, true);
+	PG_RETURN_FLOAT8(result);
+}
+
+
+/*
+ *		dcotd			- returns the cotangent of arg1 (degrees)
+ */
+Datum
+dcotd(PG_FUNCTION_ARGS)
+{
+	float8		arg1 = PG_GETARG_FLOAT8(0);
+	int			sign = 1;
+	float8		result;
+
+	/* Reduce the range of the input to [0,90] degrees */
+	arg1 = fmod(arg1, 360.0);
+	if (isnan(arg1))
+		PG_RETURN_FLOAT8(arg1);
+
+	if (arg1 < 0)
+	{
+		/* cotd(-x) = -cotd(x) */
+		arg1 = -arg1;
+		sign = -sign;
+	}
+
+	if (arg1 > 180)
+	{
+		/* cotd(360-x) = -cotd(x) */
+		arg1 = 360 - arg1;
+		sign = -sign;
+	}
+
+	if (arg1 > 90)
+	{
+		/* cotd(180-x) = -cotd(x) */
+		arg1 = 180 - arg1;
+		sign = -sign;
+	}
+
+	result = sign * cosd_q1(arg1) / sind_q1(arg1);
+	CHECKFLOATVAL(result, true /* cotd(0) == Inf */ , true);
+	PG_RETURN_FLOAT8(result);
+}
+
+
+/*
+ *		dsind			- returns the sine of arg1 (degrees)
+ */
+Datum
+dsind(PG_FUNCTION_ARGS)
+{
+	float8		arg1 = PG_GETARG_FLOAT8(0);
+	int			sign = 1;
+	float8		result;
+
+	/* Reduce the range of the input to [0,90] degrees */
+	arg1 = fmod(arg1, 360.0);
+	if (isnan(arg1))
+		PG_RETURN_FLOAT8(arg1);
+
+	if (arg1 < 0)
+	{
+		/* sind(-x) = -sind(x) */
+		arg1 = -arg1;
+		sign = -sign;
+	}
+
+	if (arg1 > 180)
+	{
+		/* sind(360-x) = -sind(x) */
+		arg1 = 360 - arg1;
+		sign = -sign;
+	}
+
+	if (arg1 > 90)
+		/* sind(180-x) = sind(x) */
+		arg1 = 180 - arg1;
+
+	result = sign * sind_q1(arg1);
+	CHECKFLOATVAL(result, false, true);
+	PG_RETURN_FLOAT8(result);
+}
+
+
+/*
+ *		dtand			- returns the tangent of arg1 (degrees)
+ */
+Datum
+dtand(PG_FUNCTION_ARGS)
+{
+	float8		arg1 = PG_GETARG_FLOAT8(0);
+	int			sign = 1;
+	float8		result;
+
+	/* Reduce the range of the input to [0,90] degrees */
+	arg1 = fmod(arg1, 360.0);
+	if (isnan(arg1))
+		PG_RETURN_FLOAT8(arg1);
+
+	if (arg1 < 0)
+	{
+		/* tand(-x) = -tand(x) */
+		arg1 = -arg1;
+		sign = -sign;
+	}
+
+	if (arg1 > 180)
+	{
+		/* tand(360-x) = -tand(x) */
+        arg1 = 360 - arg1;
+		sign = -sign;
+	}
+
+	if (arg1 > 90)
+	{
+		/* tand(180-x) = -tand(x) */
+		arg1 = 180 - arg1;
+		sign = -sign;
+	}
+
+	result = sign * sind_q1(arg1) / cosd_q1(arg1);
+	CHECKFLOATVAL(result, true /* tand(90) == Inf */ , true);
+	PG_RETURN_FLOAT8(result);
+}
+
 
 /*
  *		degrees		- returns degrees converted from radians
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
new file mode 100644
index f688454..18a9e1d
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1892,6 +1892,24 @@ DATA(insert OID = 1606 (  tan				PGNSP P
 DESCR("tangent");
 DATA(insert OID = 1607 (  cot				PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701 "701" _null_ _null_ _null_ _null_ _null_ dcot _null_ _null_ _null_ ));
 DESCR("cotangent");
+
+DATA(insert OID = 3300 (  asind				PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701 "701" _null_ _null_ _null_ _null_ _null_ dasind _null_ _null_ _null_ ));
+DESCR("arcsine, degrees");
+DATA(insert OID = 3308 (  acosd				PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701 "701" _null_ _null_ _null_ _null_ _null_ dacosd _null_ _null_ _null_ ));
+DESCR("arccosine, degrees");
+DATA(insert OID = 3309 (  atand				PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701 "701" _null_ _null_ _null_ _null_ _null_ datand _null_ _null_ _null_ ));
+DESCR("arctangent, degrees");
+DATA(insert OID = 3315 (  atan2d			PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 701 "701 701" _null_ _null_ _null_ _null_ _null_ datan2d _null_ _null_ _null_ ));
+DESCR("arctangent, two arguments, degrees");
+DATA(insert OID = 3316 (  sind				PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701 "701" _null_ _null_ _null_ _null_ _null_ dsind _null_ _null_ _null_ ));
+DESCR("sine, degrees");
+DATA(insert OID = 3317 (  cosd				PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701 "701" _null_ _null_ _null_ _null_ _null_ dcosd _null_ _null_ _null_ ));
+DESCR("cosine, degrees");
+DATA(insert OID = 3318 (  tand				PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701 "701" _null_ _null_ _null_ _null_ _null_ dtand _null_ _null_ _null_ ));
+DESCR("tangent, degrees");
+DATA(insert OID = 3319 (  cotd				PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701 "701" _null_ _null_ _null_ _null_ _null_ dcotd _null_ _null_ _null_ ));
+DESCR("cotangent, degrees");
+
 DATA(insert OID = 1608 (  degrees			PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701 "701" _null_ _null_ _null_ _null_ _null_ degrees _null_ _null_ _null_ ));
 DESCR("radians to degrees");
 DATA(insert OID = 1609 (  radians			PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701 "701" _null_ _null_ _null_ _null_ _null_ radians _null_ _null_ _null_ ));
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
new file mode 100644
index fc1679e..62751ec
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -407,6 +407,14 @@ extern Datum dcos(PG_FUNCTION_ARGS);
 extern Datum dcot(PG_FUNCTION_ARGS);
 extern Datum dsin(PG_FUNCTION_ARGS);
 extern Datum dtan(PG_FUNCTION_ARGS);
+extern Datum dacosd(PG_FUNCTION_ARGS);
+extern Datum dasind(PG_FUNCTION_ARGS);
+extern Datum datand(PG_FUNCTION_ARGS);
+extern Datum datan2d(PG_FUNCTION_ARGS);
+extern Datum dcosd(PG_FUNCTION_ARGS);
+extern Datum dcotd(PG_FUNCTION_ARGS);
+extern Datum dsind(PG_FUNCTION_ARGS);
+extern Datum dtand(PG_FUNCTION_ARGS);
 extern Datum degrees(PG_FUNCTION_ARGS);
 extern Datum dpi(PG_FUNCTION_ARGS);
 extern Datum radians(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/float8.out b/src/test/regress/expected/float8.out
new file mode 100644
index 6221538..a6261d2
--- a/src/test/regress/expected/float8.out
+++ b/src/test/regress/expected/float8.out
@@ -444,3 +444,330 @@ SELECT '' AS five, * FROM FLOAT8_TBL;
       | -1.2345678901234e-200
 (5 rows)
 
+-- Test the trigonometric functions.
+-- Use a lower precision to eliminate variations between platforms.
+SET extra_float_digits TO -5;
+-- sine and cosine
+SELECT x,
+       sin(radians(x)) AS sin_radians, sind(x),
+       cos(radians(x)) AS cos_radians, cosd(x)
+FROM generate_series(-360, 360, 10) AS t(x)
+WHERE x = 0 OR x % 90 != 0; -- skip tests that are very platform-dependent
+  x   |  sin_radians  |     sind      |  cos_radians  |     cosd      
+------+---------------+---------------+---------------+---------------
+ -350 |  0.1736481777 |  0.1736481777 |   0.984807753 |   0.984807753
+ -340 |  0.3420201433 |  0.3420201433 |  0.9396926208 |  0.9396926208
+ -330 |           0.5 |           0.5 |  0.8660254038 |  0.8660254038
+ -320 |  0.6427876097 |  0.6427876097 |  0.7660444431 |  0.7660444431
+ -310 |  0.7660444431 |  0.7660444431 |  0.6427876097 |  0.6427876097
+ -300 |  0.8660254038 |  0.8660254038 |           0.5 |           0.5
+ -290 |  0.9396926208 |  0.9396926208 |  0.3420201433 |  0.3420201433
+ -280 |   0.984807753 |   0.984807753 |  0.1736481777 |  0.1736481777
+ -260 |   0.984807753 |   0.984807753 | -0.1736481777 | -0.1736481777
+ -250 |  0.9396926208 |  0.9396926208 | -0.3420201433 | -0.3420201433
+ -240 |  0.8660254038 |  0.8660254038 |          -0.5 |          -0.5
+ -230 |  0.7660444431 |  0.7660444431 | -0.6427876097 | -0.6427876097
+ -220 |  0.6427876097 |  0.6427876097 | -0.7660444431 | -0.7660444431
+ -210 |           0.5 |           0.5 | -0.8660254038 | -0.8660254038
+ -200 |  0.3420201433 |  0.3420201433 | -0.9396926208 | -0.9396926208
+ -190 |  0.1736481777 |  0.1736481777 |  -0.984807753 |  -0.984807753
+ -170 | -0.1736481777 | -0.1736481777 |  -0.984807753 |  -0.984807753
+ -160 | -0.3420201433 | -0.3420201433 | -0.9396926208 | -0.9396926208
+ -150 |          -0.5 |          -0.5 | -0.8660254038 | -0.8660254038
+ -140 | -0.6427876097 | -0.6427876097 | -0.7660444431 | -0.7660444431
+ -130 | -0.7660444431 | -0.7660444431 | -0.6427876097 | -0.6427876097
+ -120 | -0.8660254038 | -0.8660254038 |          -0.5 |          -0.5
+ -110 | -0.9396926208 | -0.9396926208 | -0.3420201433 | -0.3420201433
+ -100 |  -0.984807753 |  -0.984807753 | -0.1736481777 | -0.1736481777
+  -80 |  -0.984807753 |  -0.984807753 |  0.1736481777 |  0.1736481777
+  -70 | -0.9396926208 | -0.9396926208 |  0.3420201433 |  0.3420201433
+  -60 | -0.8660254038 | -0.8660254038 |           0.5 |           0.5
+  -50 | -0.7660444431 | -0.7660444431 |  0.6427876097 |  0.6427876097
+  -40 | -0.6427876097 | -0.6427876097 |  0.7660444431 |  0.7660444431
+  -30 |          -0.5 |          -0.5 |  0.8660254038 |  0.8660254038
+  -20 | -0.3420201433 | -0.3420201433 |  0.9396926208 |  0.9396926208
+  -10 | -0.1736481777 | -0.1736481777 |   0.984807753 |   0.984807753
+    0 |             0 |             0 |             1 |             1
+   10 |  0.1736481777 |  0.1736481777 |   0.984807753 |   0.984807753
+   20 |  0.3420201433 |  0.3420201433 |  0.9396926208 |  0.9396926208
+   30 |           0.5 |           0.5 |  0.8660254038 |  0.8660254038
+   40 |  0.6427876097 |  0.6427876097 |  0.7660444431 |  0.7660444431
+   50 |  0.7660444431 |  0.7660444431 |  0.6427876097 |  0.6427876097
+   60 |  0.8660254038 |  0.8660254038 |           0.5 |           0.5
+   70 |  0.9396926208 |  0.9396926208 |  0.3420201433 |  0.3420201433
+   80 |   0.984807753 |   0.984807753 |  0.1736481777 |  0.1736481777
+  100 |   0.984807753 |   0.984807753 | -0.1736481777 | -0.1736481777
+  110 |  0.9396926208 |  0.9396926208 | -0.3420201433 | -0.3420201433
+  120 |  0.8660254038 |  0.8660254038 |          -0.5 |          -0.5
+  130 |  0.7660444431 |  0.7660444431 | -0.6427876097 | -0.6427876097
+  140 |  0.6427876097 |  0.6427876097 | -0.7660444431 | -0.7660444431
+  150 |           0.5 |           0.5 | -0.8660254038 | -0.8660254038
+  160 |  0.3420201433 |  0.3420201433 | -0.9396926208 | -0.9396926208
+  170 |  0.1736481777 |  0.1736481777 |  -0.984807753 |  -0.984807753
+  190 | -0.1736481777 | -0.1736481777 |  -0.984807753 |  -0.984807753
+  200 | -0.3420201433 | -0.3420201433 | -0.9396926208 | -0.9396926208
+  210 |          -0.5 |          -0.5 | -0.8660254038 | -0.8660254038
+  220 | -0.6427876097 | -0.6427876097 | -0.7660444431 | -0.7660444431
+  230 | -0.7660444431 | -0.7660444431 | -0.6427876097 | -0.6427876097
+  240 | -0.8660254038 | -0.8660254038 |          -0.5 |          -0.5
+  250 | -0.9396926208 | -0.9396926208 | -0.3420201433 | -0.3420201433
+  260 |  -0.984807753 |  -0.984807753 | -0.1736481777 | -0.1736481777
+  280 |  -0.984807753 |  -0.984807753 |  0.1736481777 |  0.1736481777
+  290 | -0.9396926208 | -0.9396926208 |  0.3420201433 |  0.3420201433
+  300 | -0.8660254038 | -0.8660254038 |           0.5 |           0.5
+  310 | -0.7660444431 | -0.7660444431 |  0.6427876097 |  0.6427876097
+  320 | -0.6427876097 | -0.6427876097 |  0.7660444431 |  0.7660444431
+  330 |          -0.5 |          -0.5 |  0.8660254038 |  0.8660254038
+  340 | -0.3420201433 | -0.3420201433 |  0.9396926208 |  0.9396926208
+  350 | -0.1736481777 | -0.1736481777 |   0.984807753 |   0.984807753
+(65 rows)
+
+-- test exactness
+SELECT x,
+       CASE WHEN sind(x) IN (-1,-0.5,0,0.5,1) THEN sind(x) END AS sind,
+       CASE WHEN cosd(x) IN (-1,-0.5,0,0.5,1) THEN cosd(x) END AS cosd
+FROM generate_series(0, 360, 30) AS t(x);
+  x  | sind | cosd 
+-----+------+------
+   0 |    0 |    1
+  30 |  0.5 |     
+  60 |      |  0.5
+  90 |    1 |    0
+ 120 |      | -0.5
+ 150 |  0.5 |     
+ 180 |    0 |   -1
+ 210 | -0.5 |     
+ 240 |      | -0.5
+ 270 |   -1 |    0
+ 300 |      |  0.5
+ 330 | -0.5 |     
+ 360 |    0 |    1
+(13 rows)
+
+-- tangent and cotangent
+SELECT x,
+       tan(radians(x)) AS tan_radians, tand(x),
+       cot(radians(x)) AS cot_radians, cotd(x)
+FROM generate_series(-360, 360, 10) AS t(x)
+WHERE x = 0 OR x % 90 != 0; -- skip tests that are very platform-dependent
+  x   |  tan_radians  |     tand      |  cot_radians  |     cotd      
+------+---------------+---------------+---------------+---------------
+ -350 |  0.1763269807 |  0.1763269807 |    5.67128182 |    5.67128182
+ -340 |  0.3639702343 |  0.3639702343 |   2.747477419 |   2.747477419
+ -330 |  0.5773502692 |  0.5773502692 |   1.732050808 |   1.732050808
+ -320 |  0.8390996312 |  0.8390996312 |   1.191753593 |   1.191753593
+ -310 |   1.191753593 |   1.191753593 |  0.8390996312 |  0.8390996312
+ -300 |   1.732050808 |   1.732050808 |  0.5773502692 |  0.5773502692
+ -290 |   2.747477419 |   2.747477419 |  0.3639702343 |  0.3639702343
+ -280 |    5.67128182 |    5.67128182 |  0.1763269807 |  0.1763269807
+ -260 |   -5.67128182 |   -5.67128182 | -0.1763269807 | -0.1763269807
+ -250 |  -2.747477419 |  -2.747477419 | -0.3639702343 | -0.3639702343
+ -240 |  -1.732050808 |  -1.732050808 | -0.5773502692 | -0.5773502692
+ -230 |  -1.191753593 |  -1.191753593 | -0.8390996312 | -0.8390996312
+ -220 | -0.8390996312 | -0.8390996312 |  -1.191753593 |  -1.191753593
+ -210 | -0.5773502692 | -0.5773502692 |  -1.732050808 |  -1.732050808
+ -200 | -0.3639702343 | -0.3639702343 |  -2.747477419 |  -2.747477419
+ -190 | -0.1763269807 | -0.1763269807 |   -5.67128182 |   -5.67128182
+ -170 |  0.1763269807 |  0.1763269807 |    5.67128182 |    5.67128182
+ -160 |  0.3639702343 |  0.3639702343 |   2.747477419 |   2.747477419
+ -150 |  0.5773502692 |  0.5773502692 |   1.732050808 |   1.732050808
+ -140 |  0.8390996312 |  0.8390996312 |   1.191753593 |   1.191753593
+ -130 |   1.191753593 |   1.191753593 |  0.8390996312 |  0.8390996312
+ -120 |   1.732050808 |   1.732050808 |  0.5773502692 |  0.5773502692
+ -110 |   2.747477419 |   2.747477419 |  0.3639702343 |  0.3639702343
+ -100 |    5.67128182 |    5.67128182 |  0.1763269807 |  0.1763269807
+  -80 |   -5.67128182 |   -5.67128182 | -0.1763269807 | -0.1763269807
+  -70 |  -2.747477419 |  -2.747477419 | -0.3639702343 | -0.3639702343
+  -60 |  -1.732050808 |  -1.732050808 | -0.5773502692 | -0.5773502692
+  -50 |  -1.191753593 |  -1.191753593 | -0.8390996312 | -0.8390996312
+  -40 | -0.8390996312 | -0.8390996312 |  -1.191753593 |  -1.191753593
+  -30 | -0.5773502692 | -0.5773502692 |  -1.732050808 |  -1.732050808
+  -20 | -0.3639702343 | -0.3639702343 |  -2.747477419 |  -2.747477419
+  -10 | -0.1763269807 | -0.1763269807 |   -5.67128182 |   -5.67128182
+    0 |             0 |             0 |      Infinity |      Infinity
+   10 |  0.1763269807 |  0.1763269807 |    5.67128182 |    5.67128182
+   20 |  0.3639702343 |  0.3639702343 |   2.747477419 |   2.747477419
+   30 |  0.5773502692 |  0.5773502692 |   1.732050808 |   1.732050808
+   40 |  0.8390996312 |  0.8390996312 |   1.191753593 |   1.191753593
+   50 |   1.191753593 |   1.191753593 |  0.8390996312 |  0.8390996312
+   60 |   1.732050808 |   1.732050808 |  0.5773502692 |  0.5773502692
+   70 |   2.747477419 |   2.747477419 |  0.3639702343 |  0.3639702343
+   80 |    5.67128182 |    5.67128182 |  0.1763269807 |  0.1763269807
+  100 |   -5.67128182 |   -5.67128182 | -0.1763269807 | -0.1763269807
+  110 |  -2.747477419 |  -2.747477419 | -0.3639702343 | -0.3639702343
+  120 |  -1.732050808 |  -1.732050808 | -0.5773502692 | -0.5773502692
+  130 |  -1.191753593 |  -1.191753593 | -0.8390996312 | -0.8390996312
+  140 | -0.8390996312 | -0.8390996312 |  -1.191753593 |  -1.191753593
+  150 | -0.5773502692 | -0.5773502692 |  -1.732050808 |  -1.732050808
+  160 | -0.3639702343 | -0.3639702343 |  -2.747477419 |  -2.747477419
+  170 | -0.1763269807 | -0.1763269807 |   -5.67128182 |   -5.67128182
+  190 |  0.1763269807 |  0.1763269807 |    5.67128182 |    5.67128182
+  200 |  0.3639702343 |  0.3639702343 |   2.747477419 |   2.747477419
+  210 |  0.5773502692 |  0.5773502692 |   1.732050808 |   1.732050808
+  220 |  0.8390996312 |  0.8390996312 |   1.191753593 |   1.191753593
+  230 |   1.191753593 |   1.191753593 |  0.8390996312 |  0.8390996312
+  240 |   1.732050808 |   1.732050808 |  0.5773502692 |  0.5773502692
+  250 |   2.747477419 |   2.747477419 |  0.3639702343 |  0.3639702343
+  260 |    5.67128182 |    5.67128182 |  0.1763269807 |  0.1763269807
+  280 |   -5.67128182 |   -5.67128182 | -0.1763269807 | -0.1763269807
+  290 |  -2.747477419 |  -2.747477419 | -0.3639702343 | -0.3639702343
+  300 |  -1.732050808 |  -1.732050808 | -0.5773502692 | -0.5773502692
+  310 |  -1.191753593 |  -1.191753593 | -0.8390996312 | -0.8390996312
+  320 | -0.8390996312 | -0.8390996312 |  -1.191753593 |  -1.191753593
+  330 | -0.5773502692 | -0.5773502692 |  -1.732050808 |  -1.732050808
+  340 | -0.3639702343 | -0.3639702343 |  -2.747477419 |  -2.747477419
+  350 | -0.1763269807 | -0.1763269807 |   -5.67128182 |   -5.67128182
+(65 rows)
+
+-- test exactness
+SELECT x,
+       CASE WHEN tand(x) IN (-1,0,1) THEN tand(x) END AS tand,
+       CASE WHEN cotd(x) IN (-1,0,1) THEN cotd(x) END AS cotd
+FROM generate_series(0, 360, 45) AS t(x);
+  x  | tand | cotd 
+-----+------+------
+   0 |    0 |     
+  45 |    1 |    1
+  90 |      |    0
+ 135 |   -1 |   -1
+ 180 |   -0 |     
+ 225 |    1 |    1
+ 270 |      |   -0
+ 315 |   -1 |   -1
+ 360 |    0 |     
+(9 rows)
+
+-- arcsine and arccosine
+SELECT x,
+       degrees(asin(x)) AS degrees_asin, asind(x),
+       degrees(acos(x)) AS degrees_acos, acosd(x)
+FROM generate_series(-1.0, 1.0, 0.1) AS t(x);
+  x   | degrees_asin |    asind     | degrees_acos |    acosd    
+------+--------------+--------------+--------------+-------------
+ -1.0 |          -90 |          -90 |          180 |         180
+ -0.9 | -64.15806724 | -64.15806724 |  154.1580672 | 154.1580672
+ -0.8 | -53.13010235 | -53.13010235 |  143.1301024 | 143.1301024
+ -0.7 |   -44.427004 |   -44.427004 |   134.427004 |  134.427004
+ -0.6 | -36.86989765 | -36.86989765 |  126.8698976 | 126.8698976
+ -0.5 |          -30 |          -30 |          120 |         120
+ -0.4 | -23.57817848 | -23.57817848 |  113.5781785 | 113.5781785
+ -0.3 | -17.45760312 | -17.45760312 |  107.4576031 | 107.4576031
+ -0.2 | -11.53695903 | -11.53695903 |   101.536959 |  101.536959
+ -0.1 | -5.739170477 | -5.739170477 |  95.73917048 | 95.73917048
+  0.0 |            0 |            0 |           90 |          90
+  0.1 |  5.739170477 |  5.739170477 |  84.26082952 | 84.26082952
+  0.2 |  11.53695903 |  11.53695903 |  78.46304097 | 78.46304097
+  0.3 |  17.45760312 |  17.45760312 |  72.54239688 | 72.54239688
+  0.4 |  23.57817848 |  23.57817848 |  66.42182152 | 66.42182152
+  0.5 |           30 |           30 |           60 |          60
+  0.6 |  36.86989765 |  36.86989765 |  53.13010235 | 53.13010235
+  0.7 |    44.427004 |    44.427004 |    45.572996 |   45.572996
+  0.8 |  53.13010235 |  53.13010235 |  36.86989765 | 36.86989765
+  0.9 |  64.15806724 |  64.15806724 |  25.84193276 | 25.84193276
+  1.0 |           90 |           90 |            0 |           0
+(21 rows)
+
+-- arctangent
+SELECT x, degrees(atan(x)) AS degrees_atan, atand(x)
+FROM (SELECT '-Infinity'::float8
+      UNION ALL
+      SELECT generate_series(-10, -2)
+      UNION ALL
+      SELECT generate_series(-1.0, 1.0, 0.1)
+      UNION ALL
+      SELECT generate_series(2, 10)
+      UNION ALL
+      SELECT 'Infinity'::float8
+      UNION ALL
+      SELECT 'NaN'::float8) AS t(x);
+     x     | degrees_atan |    atand     
+-----------+--------------+--------------
+ -Infinity |          -90 |          -90
+       -10 | -84.28940686 | -84.28940686
+        -9 | -83.65980825 | -83.65980825
+        -8 | -82.87498365 | -82.87498365
+        -7 | -81.86989765 | -81.86989765
+        -6 | -80.53767779 | -80.53767779
+        -5 | -78.69006753 | -78.69006753
+        -4 | -75.96375653 | -75.96375653
+        -3 | -71.56505118 | -71.56505118
+        -2 | -63.43494882 | -63.43494882
+        -1 |          -45 |          -45
+      -0.9 |  -41.9872125 |  -41.9872125
+      -0.8 | -38.65980825 | -38.65980825
+      -0.7 |  -34.9920202 |  -34.9920202
+      -0.6 | -30.96375653 | -30.96375653
+      -0.5 | -26.56505118 | -26.56505118
+      -0.4 | -21.80140949 | -21.80140949
+      -0.3 | -16.69924423 | -16.69924423
+      -0.2 | -11.30993247 | -11.30993247
+      -0.1 | -5.710593137 | -5.710593137
+         0 |            0 |            0
+       0.1 |  5.710593137 |  5.710593137
+       0.2 |  11.30993247 |  11.30993247
+       0.3 |  16.69924423 |  16.69924423
+       0.4 |  21.80140949 |  21.80140949
+       0.5 |  26.56505118 |  26.56505118
+       0.6 |  30.96375653 |  30.96375653
+       0.7 |   34.9920202 |   34.9920202
+       0.8 |  38.65980825 |  38.65980825
+       0.9 |   41.9872125 |   41.9872125
+         1 |           45 |           45
+         2 |  63.43494882 |  63.43494882
+         3 |  71.56505118 |  71.56505118
+         4 |  75.96375653 |  75.96375653
+         5 |  78.69006753 |  78.69006753
+         6 |  80.53767779 |  80.53767779
+         7 |  81.86989765 |  81.86989765
+         8 |  82.87498365 |  82.87498365
+         9 |  83.65980825 |  83.65980825
+        10 |  84.28940686 |  84.28940686
+  Infinity |           90 |           90
+       NaN |          NaN |          NaN
+(42 rows)
+
+-- 4 quadrant arctangent
+SELECT x, y, degrees(atan2(y, x)) AS degrees_atan2, atan2d(y, x)
+FROM (SELECT 10*cosd(a), 10*sind(a)
+      FROM generate_series(0, 360, 10) AS t(a)) AS t(x,y);
+      x       |      y       | degrees_atan2 | atan2d 
+--------------+--------------+---------------+--------
+           10 |            0 |             0 |      0
+   9.84807753 |  1.736481777 |            10 |     10
+  9.396926208 |  3.420201433 |            20 |     20
+  8.660254038 |            5 |            30 |     30
+  7.660444431 |  6.427876097 |            40 |     40
+  6.427876097 |  7.660444431 |            50 |     50
+            5 |  8.660254038 |            60 |     60
+  3.420201433 |  9.396926208 |            70 |     70
+  1.736481777 |   9.84807753 |            80 |     80
+            0 |           10 |            90 |     90
+ -1.736481777 |   9.84807753 |           100 |    100
+ -3.420201433 |  9.396926208 |           110 |    110
+           -5 |  8.660254038 |           120 |    120
+ -6.427876097 |  7.660444431 |           130 |    130
+ -7.660444431 |  6.427876097 |           140 |    140
+ -8.660254038 |            5 |           150 |    150
+ -9.396926208 |  3.420201433 |           160 |    160
+  -9.84807753 |  1.736481777 |           170 |    170
+          -10 |            0 |           180 |    180
+  -9.84807753 | -1.736481777 |          -170 |   -170
+ -9.396926208 | -3.420201433 |          -160 |   -160
+ -8.660254038 |           -5 |          -150 |   -150
+ -7.660444431 | -6.427876097 |          -140 |   -140
+ -6.427876097 | -7.660444431 |          -130 |   -130
+           -5 | -8.660254038 |          -120 |   -120
+ -3.420201433 | -9.396926208 |          -110 |   -110
+ -1.736481777 |  -9.84807753 |          -100 |   -100
+            0 |          -10 |           -90 |    -90
+  1.736481777 |  -9.84807753 |           -80 |    -80
+  3.420201433 | -9.396926208 |           -70 |    -70
+            5 | -8.660254038 |           -60 |    -60
+  6.427876097 | -7.660444431 |           -50 |    -50
+  7.660444431 | -6.427876097 |           -40 |    -40
+  8.660254038 |           -5 |           -30 |    -30
+  9.396926208 | -3.420201433 |           -20 |    -20
+   9.84807753 | -1.736481777 |           -10 |    -10
+           10 |            0 |             0 |      0
+(37 rows)
+
diff --git a/src/test/regress/sql/float8.sql b/src/test/regress/sql/float8.sql
new file mode 100644
index 92a574a..49363d0
--- a/src/test/regress/sql/float8.sql
+++ b/src/test/regress/sql/float8.sql
@@ -167,3 +167,58 @@ INSERT INTO FLOAT8_TBL(f1) VALUES ('-1.2
 INSERT INTO FLOAT8_TBL(f1) VALUES ('-1.2345678901234e-200');
 
 SELECT '' AS five, * FROM FLOAT8_TBL;
+
+-- Test the trigonometric functions.
+-- Use a lower precision to eliminate variations between platforms.
+SET extra_float_digits TO -5;
+
+-- sine and cosine
+SELECT x,
+       sin(radians(x)) AS sin_radians, sind(x),
+       cos(radians(x)) AS cos_radians, cosd(x)
+FROM generate_series(-360, 360, 10) AS t(x)
+WHERE x = 0 OR x % 90 != 0; -- skip tests that are very platform-dependent
+
+-- test exactness
+SELECT x,
+       CASE WHEN sind(x) IN (-1,-0.5,0,0.5,1) THEN sind(x) END AS sind,
+       CASE WHEN cosd(x) IN (-1,-0.5,0,0.5,1) THEN cosd(x) END AS cosd
+FROM generate_series(0, 360, 30) AS t(x);
+
+-- tangent and cotangent
+SELECT x,
+       tan(radians(x)) AS tan_radians, tand(x),
+       cot(radians(x)) AS cot_radians, cotd(x)
+FROM generate_series(-360, 360, 10) AS t(x)
+WHERE x = 0 OR x % 90 != 0; -- skip tests that are very platform-dependent
+
+-- test exactness
+SELECT x,
+       CASE WHEN tand(x) IN (-1,0,1) THEN tand(x) END AS tand,
+       CASE WHEN cotd(x) IN (-1,0,1) THEN cotd(x) END AS cotd
+FROM generate_series(0, 360, 45) AS t(x);
+
+-- arcsine and arccosine
+SELECT x,
+       degrees(asin(x)) AS degrees_asin, asind(x),
+       degrees(acos(x)) AS degrees_acos, acosd(x)
+FROM generate_series(-1.0, 1.0, 0.1) AS t(x);
+
+-- arctangent
+SELECT x, degrees(atan(x)) AS degrees_atan, atand(x)
+FROM (SELECT '-Infinity'::float8
+      UNION ALL
+      SELECT generate_series(-10, -2)
+      UNION ALL
+      SELECT generate_series(-1.0, 1.0, 0.1)
+      UNION ALL
+      SELECT generate_series(2, 10)
+      UNION ALL
+      SELECT 'Infinity'::float8
+      UNION ALL
+      SELECT 'NaN'::float8) AS t(x);
+
+-- 4 quadrant arctangent
+SELECT x, y, degrees(atan2(y, x)) AS degrees_atan2, atan2d(y, x)
+FROM (SELECT 10*cosd(a), 10*sind(a)
+      FROM generate_series(0, 360, 10) AS t(a)) AS t(x,y);
#30David Fetter
david@fetter.org
In reply to: Nathan Wagner (#26)
1 attachment(s)
Re: fortnight interval support

On Tue, Oct 27, 2015 at 07:30:13PM +0000, Nathan Wagner wrote:

On Wed, Oct 28, 2015 at 08:17:25AM +1300, Gavin Flower wrote:

You trying to get PostgreSQL banned in France??? :-)

When I was learning French many years ago, I was told that the French
consider their fortnight to be 15 days!!!

What, it's a "fortnight", not a "quinzaine".

You have no idea how hard it was to resist updating the patch...

Well, if you won't do it, I will.

Please find attached.

Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

Attachments:

fortnight_quinzaine.patchtext/plain; charset=us-asciiDownload
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 926358e..8a62d65 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -186,6 +186,8 @@ static const datetkn deltatktbl[] = {
 	{DDECADE, UNITS, DTK_DECADE},		/* "decade" relative */
 	{"decades", UNITS, DTK_DECADE},		/* "decades" relative */
 	{"decs", UNITS, DTK_DECADE},	/* "decades" relative */
+	{DFORTNIGHT, UNITS, DTK_FORTNIGHT}, /* "fortnights" relative */
+	{"fortnights", UNITS, DTK_FORTNIGHT}, /* "fortnights" relative */
 	{"h", UNITS, DTK_HOUR},		/* "hour" relative */
 	{DHOUR, UNITS, DTK_HOUR},	/* "hour" relative */
 	{"hours", UNITS, DTK_HOUR}, /* "hours" relative */
@@ -214,6 +216,8 @@ static const datetkn deltatktbl[] = {
 	{"msecs", UNITS, DTK_MILLISEC},
 	{"qtr", UNITS, DTK_QUARTER},	/* "quarter" relative */
 	{DQUARTER, UNITS, DTK_QUARTER},		/* "quarter" relative */
+	{DQUINZAINE, UNITS, DTK_QUINZAINE}, /* "quinzaines" relative */
+	{"quinzaines", UNITS, DTK_QUINZAINE}, /* "quinzaines" relative */
 	{"s", UNITS, DTK_SECOND},
 	{"sec", UNITS, DTK_SECOND},
 	{DSECOND, UNITS, DTK_SECOND},
@@ -3281,6 +3285,18 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 						tmask = DTK_M(DAY);
 						break;
 
+					case DTK_FORTNIGHT:
+						tm->tm_mday += val * 14;
+						AdjustFractDays(fval, tm, fsec, 14);
+						tmask = DTK_M(WEEK);
+						break;
+
+					case DTK_QUINZAINE:
+						tm->tm_mday += val * 15;
+						AdjustFractDays(fval, tm, fsec, 15);
+						tmask = DTK_M(WEEK);
+						break;
+
 					case DTK_WEEK:
 						tm->tm_mday += val * 7;
 						AdjustFractDays(fval, tm, fsec, 7);
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index e9a1ece..3944912 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -52,6 +52,8 @@ struct tzEntry;
 #define DHOUR			"hour"
 #define DDAY			"day"
 #define DWEEK			"week"
+#define DFORTNIGHT		"fortnight"
+#define DQUINZAINE		"quinzaine"
 #define DMONTH			"month"
 #define DQUARTER		"quarter"
 #define DYEAR			"year"
@@ -181,6 +183,8 @@ struct tzEntry;
 #define DTK_TZ_MINUTE	35
 #define DTK_ISOYEAR		36
 #define DTK_ISODOW		37
+#define DTK_FORTNIGHT		38
+#define DTK_QUINZAINE		39
 
 
 /*
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index c873a99..350f097 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -40,6 +40,18 @@ SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
  10 days 12:00:00
 (1 row)
 
+SELECT INTERVAL '1 fortnight' AS "Fourteen days";
+ Fourteen days 
+---------------
+ 14 days
+(1 row)
+
+SELECT INTERVAL '1 quinzaine' AS "Fifteen days";
+ Fifteen days 
+--------------
+ 15 days
+(1 row)
+
 SELECT INTERVAL '1.5 months' AS "One month 15 days";
  One month 15 days 
 -------------------
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 789c3de..ae884bc 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -12,6 +12,8 @@ SELECT INTERVAL '-08:00' AS "Eight hours";
 SELECT INTERVAL '-1 +02:03' AS "22 hours ago...";
 SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
 SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
+SELECT INTERVAL '1 fortnight' AS "Fourteen days";
+SELECT INTERVAL '1 quinzaine' AS "Fifteen days";
 SELECT INTERVAL '1.5 months' AS "One month 15 days";
 SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
 
#31Tom Lane
tgl@sss.pgh.pa.us
In reply to: David Fetter (#30)
Re: fortnight interval support

David Fetter <david@fetter.org> writes:

On Tue, Oct 27, 2015 at 07:30:13PM +0000, Nathan Wagner wrote:

On Wed, Oct 28, 2015 at 08:17:25AM +1300, Gavin Flower wrote:
What, it's a "fortnight", not a "quinzaine".

You have no idea how hard it was to resist updating the patch...

Well, if you won't do it, I will.

Please tell me this is a joke.

(FWIW, I don't have a problem with "fortnight", but I draw the line
at units that are not used in English.)

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#32David Fetter
david@fetter.org
In reply to: Tom Lane (#31)
Re: fortnight interval support

On Sun, Nov 01, 2015 at 01:06:39PM -0500, Tom Lane wrote:

David Fetter <david@fetter.org> writes:

On Tue, Oct 27, 2015 at 07:30:13PM +0000, Nathan Wagner wrote:

On Wed, Oct 28, 2015 at 08:17:25AM +1300, Gavin Flower wrote:
What, it's a "fortnight", not a "quinzaine".

You have no idea how hard it was to resist updating the patch...

Well, if you won't do it, I will.

Please tell me this is a joke.

Yes, it's a joke.

(FWIW, I don't have a problem with "fortnight", but I draw the line
at units that are not used in English.)

It's used in English, but not in this context.

https://en.wikipedia.org/wiki/Quinzaine

As to localization, I think we need to consider carefully whether
PostgreSQL is to be a US-only RDBMS with a few concessions to usage
elsewhere, or one usable by all the world's peoples, and no, we should
probably not continue this thread to work out the decisions implicit
in whatever direction we settle on.

Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#33Merlin Moncure
mmoncure@gmail.com
In reply to: David Fetter (#32)
Re: fortnight interval support

On Sun, Nov 1, 2015 at 2:20 PM, David Fetter <david@fetter.org> wrote:

On Sun, Nov 01, 2015 at 01:06:39PM -0500, Tom Lane wrote:

David Fetter <david@fetter.org> writes:

On Tue, Oct 27, 2015 at 07:30:13PM +0000, Nathan Wagner wrote:

On Wed, Oct 28, 2015 at 08:17:25AM +1300, Gavin Flower wrote:
What, it's a "fortnight", not a "quinzaine".

You have no idea how hard it was to resist updating the patch...

Well, if you won't do it, I will.

Please tell me this is a joke.

Yes, it's a joke.

(FWIW, I don't have a problem with "fortnight", but I draw the line
at units that are not used in English.)

It's used in English, but not in this context.

https://en.wikipedia.org/wiki/Quinzaine

As to localization, I think we need to consider carefully whether
PostgreSQL is to be a US-only RDBMS with a few concessions to usage
elsewhere, or one usable by all the world's peoples

That baggage comes with the SQL Standard. It's fun to think about a
functional equivalent of SQL that doesn't attempt to have statements
be grammatically correct English sentences but due to various human
pressures that isn't how things worked out. Since I'm daydreaming,
let's have this hypothetical language implement relational division.

By the way, I fell for the joke. Note, we do implement 'allballs' so
I'll take a pass.

merlin

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#34Andrew Dunstan
andrew@dunslane.net
In reply to: Merlin Moncure (#33)
Re: fortnight interval support

On 11/02/2015 09:20 AM, Merlin Moncure wrote:

On Sun, Nov 1, 2015 at 2:20 PM, David Fetter <david@fetter.org> wrote:

On Sun, Nov 01, 2015 at 01:06:39PM -0500, Tom Lane wrote:

David Fetter <david@fetter.org> writes:

On Tue, Oct 27, 2015 at 07:30:13PM +0000, Nathan Wagner wrote:

On Wed, Oct 28, 2015 at 08:17:25AM +1300, Gavin Flower wrote:
What, it's a "fortnight", not a "quinzaine".

You have no idea how hard it was to resist updating the patch...

Well, if you won't do it, I will.

Please tell me this is a joke.

Yes, it's a joke.

(FWIW, I don't have a problem with "fortnight", but I draw the line
at units that are not used in English.)

It's used in English, but not in this context.

https://en.wikipedia.org/wiki/Quinzaine

As to localization, I think we need to consider carefully whether
PostgreSQL is to be a US-only RDBMS with a few concessions to usage
elsewhere, or one usable by all the world's peoples

That baggage comes with the SQL Standard. It's fun to think about a
functional equivalent of SQL that doesn't attempt to have statements
be grammatically correct English sentences but due to various human
pressures that isn't how things worked out. Since I'm daydreaming,
let's have this hypothetical language implement relational division.

By the way, I fell for the joke. Note, we do implement 'allballs' so
I'll take a pass.

I respectfully submit that there is a case for supporting lovers of Jane
Austen (such as me!) by recognizing "se'nnight", with or without
apostrophe, as a synonym for "week".

cheers

andrew

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#35Gavin Flower
GavinFlower@archidevsys.co.nz
In reply to: Tom Lane (#31)
Re: fortnight interval support

On 02/11/15 07:06, Tom Lane wrote:

David Fetter <david@fetter.org> writes:

On Tue, Oct 27, 2015 at 07:30:13PM +0000, Nathan Wagner wrote:

On Wed, Oct 28, 2015 at 08:17:25AM +1300, Gavin Flower wrote:

I had actually written:

You trying to get PostgreSQL banned in France???

When I was learning French many years ago, I was told that the French
consider their fortnight to be 15 days!!!

The following 2 lines were from Nathan, not me!

What, it's a "fortnight", not a "quinzaine".

You have no idea how hard it was to resist updating the patch...

Well, if you won't do it, I will.

Please tell me this is a joke.

(FWIW, I don't have a problem with "fortnight", but I draw the line
at units that are not used in English.)

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#36Michael Paquier
michael.paquier@gmail.com
In reply to: Gavin Flower (#25)
Re: fortnight interval support

On Wed, Oct 28, 2015 at 4:17 AM, Gavin Flower wrote:

You trying to get PostgreSQL banned in France??? :-)

When I was learning French many years ago, I was told that the French
consider their fortnight to be 15 days!!!

Confirmed. I would translate fornight as 'quinzaine' to French.
(please let's not add that btw).
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#37Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Michael Paquier (#36)
Re: fortnight interval support

Michael Paquier wrote:

On Wed, Oct 28, 2015 at 4:17 AM, Gavin Flower wrote:

You trying to get PostgreSQL banned in France??? :-)

When I was learning French many years ago, I was told that the French
consider their fortnight to be 15 days!!!

Confirmed. I would translate fornight as 'quinzaine' to French.
(please let's not add that btw).

I don't know what's the origin of the word "fortnight", in particular
whether it's always supposed to be exactly fourteen days, but the
equivalent to quizaine in Spanish ("quincena") is ambiguous enough that
I would have trouble making it part of intervals in Postgres. It is
sometimes interpreted as "two weeks", other times as exactly fifteen
days, other times as "half a month".

(WRT the reference to Jane Austen and "Se'ennight" for "week", it occurs
to me that fortnight is a similar contraction for "forteen night".)

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#38Robert Haas
robertmhaas@gmail.com
In reply to: Alvaro Herrera (#37)
Re: fortnight interval support

On Tue, Nov 3, 2015 at 8:31 AM, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

(WRT the reference to Jane Austen and "Se'ennight" for "week", it occurs
to me that fortnight is a similar contraction for "forteen night".)

Well, clearly we also need enquië for the elves of Arda and tenday for
for Faerûnians. Seems like a serious oversight, though making enquië
work in non-Unicode locales might be tricky.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#39Michael Paquier
michael.paquier@gmail.com
In reply to: Robert Haas (#38)
Re: fortnight interval support

On Wed, Nov 4, 2015 at 2:31 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Tue, Nov 3, 2015 at 8:31 AM, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

(WRT the reference to Jane Austen and "Se'ennight" for "week", it occurs
to me that fortnight is a similar contraction for "forteen night".)

Well, clearly we also need enquië for the elves of Arda and tenday for
for Faerûnians. Seems like a serious oversight, though making enquië
work in non-Unicode locales might be tricky.

You are forgetting Klingon, parseltongue, dwarfic and the one of
Mordor. It's not worth angering them as well. But I'll stop here.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#40Robert Haas
robertmhaas@gmail.com
In reply to: Michael Paquier (#39)
Re: fortnight interval support

On Tue, Nov 3, 2015 at 6:40 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:

On Wed, Nov 4, 2015 at 2:31 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Tue, Nov 3, 2015 at 8:31 AM, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

(WRT the reference to Jane Austen and "Se'ennight" for "week", it occurs
to me that fortnight is a similar contraction for "forteen night".)

Well, clearly we also need enquië for the elves of Arda and tenday for
for Faerûnians. Seems like a serious oversight, though making enquië
work in non-Unicode locales might be tricky.

You are forgetting Klingon, parseltongue, dwarfic and the one of
Mordor. It's not worth angering them as well. But I'll stop here.

I'm not aware that any of those have weeks of some duration other than
7 days, but the lack of support for the Klingon calendar in general is
indeed troubling.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#41Michael Paquier
michael.paquier@gmail.com
In reply to: Dean Rasheed (#29)
1 attachment(s)
Re: Proposal: Trigonometric functions in degrees

On Sun, Nov 1, 2015 at 9:34 PM, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

On 27 October 2015 at 08:24, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

I think it's still feasible to have sind(30) = 0.5 exactly and keep
monotonicity....

Here's a patch along those lines. It turned out to be fairly
straightforward. It's still basically a thin wrapper on top of the
math library trig functions, with a bit of careful scaling to ensure
that curves join together to form continuous functions that are
monotonic in the expected regions and return exact values in all the
special cases 0,30,45,60,90,...

I also modified some of the CHECKFLOATVAL() checks which didn't look
right to me, unless there's some odd platform-specific behaviour that
I'm not aware of, functions like sin and asin should never return
infinity.

The alternative expected outputs for test float8 need to be updated,
this is causing regression failures particularly on win32 where 3
digits are used for exponentials and where tan('NaN') actually results
in an ERROR. See for example the attached regressions.diffs.
--
Michael

Attachments:

regression.diffsapplication/octet-stream; name=regression.diffsDownload
*** C:/Users/ioltas/git/postgres/src/test/regress/expected/float8.out	Tue Nov 10 05:37:16 2015
--- C:/Users/ioltas/git/postgres/src/test/regress/results/float8.out	Tue Nov 10 05:55:15 2015
***************
*** 360,371 ****
  
  SELECT '' AS five, f.f1, ||/f.f1 AS cbrt_f1 FROM FLOAT8_TBL f;
   five |          f1          |       cbrt_f1        
! ------+----------------------+----------------------
        |                    0 |                    0
        |               1004.3 |      10.014312837827
        |               -34.84 |    -3.26607421344208
!       | 1.2345678901234e+200 | 4.97933859234765e+66
!       | 1.2345678901234e-200 |  2.3112042409018e-67
  (5 rows)
  
  SELECT '' AS five, * FROM FLOAT8_TBL;
--- 360,371 ----
  
  SELECT '' AS five, f.f1, ||/f.f1 AS cbrt_f1 FROM FLOAT8_TBL f;
   five |          f1          |        cbrt_f1        
! ------+----------------------+-----------------------
        |                    0 |                     0
        |               1004.3 |       10.014312837827
        |               -34.84 |     -3.26607421344208
!       | 1.2345678901234e+200 | 4.97933859234765e+066
!       | 1.2345678901234e-200 |  2.3112042409018e-067
  (5 rows)
  
  SELECT '' AS five, * FROM FLOAT8_TBL;
***************
*** 680,731 ****
        SELECT 'Infinity'::float8
        UNION ALL
        SELECT 'NaN'::float8) AS t(x);
!      x     | degrees_atan |    atand     
! -----------+--------------+--------------
!  -Infinity |          -90 |          -90
!        -10 | -84.28940686 | -84.28940686
!         -9 | -83.65980825 | -83.65980825
!         -8 | -82.87498365 | -82.87498365
!         -7 | -81.86989765 | -81.86989765
!         -6 | -80.53767779 | -80.53767779
!         -5 | -78.69006753 | -78.69006753
!         -4 | -75.96375653 | -75.96375653
!         -3 | -71.56505118 | -71.56505118
!         -2 | -63.43494882 | -63.43494882
!         -1 |          -45 |          -45
!       -0.9 |  -41.9872125 |  -41.9872125
!       -0.8 | -38.65980825 | -38.65980825
!       -0.7 |  -34.9920202 |  -34.9920202
!       -0.6 | -30.96375653 | -30.96375653
!       -0.5 | -26.56505118 | -26.56505118
!       -0.4 | -21.80140949 | -21.80140949
!       -0.3 | -16.69924423 | -16.69924423
!       -0.2 | -11.30993247 | -11.30993247
!       -0.1 | -5.710593137 | -5.710593137
!          0 |            0 |            0
!        0.1 |  5.710593137 |  5.710593137
!        0.2 |  11.30993247 |  11.30993247
!        0.3 |  16.69924423 |  16.69924423
!        0.4 |  21.80140949 |  21.80140949
!        0.5 |  26.56505118 |  26.56505118
!        0.6 |  30.96375653 |  30.96375653
!        0.7 |   34.9920202 |   34.9920202
!        0.8 |  38.65980825 |  38.65980825
!        0.9 |   41.9872125 |   41.9872125
!          1 |           45 |           45
!          2 |  63.43494882 |  63.43494882
!          3 |  71.56505118 |  71.56505118
!          4 |  75.96375653 |  75.96375653
!          5 |  78.69006753 |  78.69006753
!          6 |  80.53767779 |  80.53767779
!          7 |  81.86989765 |  81.86989765
!          8 |  82.87498365 |  82.87498365
!          9 |  83.65980825 |  83.65980825
!         10 |  84.28940686 |  84.28940686
!   Infinity |           90 |           90
!        NaN |          NaN |          NaN
! (42 rows)
! 
  -- 4 quadrant arctangent
  SELECT x, y, degrees(atan2(y, x)) AS degrees_atan2, atan2d(y, x)
  FROM (SELECT 10*cosd(a), 10*sind(a)
--- 680,686 ----
        SELECT 'Infinity'::float8
        UNION ALL
        SELECT 'NaN'::float8) AS t(x);
! ERROR:  input is out of range
  -- 4 quadrant arctangent
  SELECT x, y, degrees(atan2(y, x)) AS degrees_atan2, atan2d(y, x)
  FROM (SELECT 10*cosd(a), 10*sind(a)

======================================================================

#42Michael Paquier
michael.paquier@gmail.com
In reply to: Michael Paquier (#41)
2 attachment(s)
Re: Proposal: Trigonometric functions in degrees

On Tue, Nov 10, 2015 at 11:17 PM, Michael Paquier <michael.paquier@gmail.com>
wrote:

On Sun, Nov 1, 2015 at 9:34 PM, Dean Rasheed <dean.a.rasheed@gmail.com>

wrote:

On 27 October 2015 at 08:24, Dean Rasheed <dean.a.rasheed@gmail.com>

wrote:

I think it's still feasible to have sind(30) = 0.5 exactly and keep
monotonicity....

Here's a patch along those lines. It turned out to be fairly
straightforward. It's still basically a thin wrapper on top of the
math library trig functions, with a bit of careful scaling to ensure
that curves join together to form continuous functions that are
monotonic in the expected regions and return exact values in all the
special cases 0,30,45,60,90,...

I also modified some of the CHECKFLOATVAL() checks which didn't look
right to me, unless there's some odd platform-specific behaviour that
I'm not aware of, functions like sin and asin should never return
infinity.

-       CHECKFLOATVAL(result, isinf(arg1), true);
+       CHECKFLOATVAL(result, false, true);
        PG_RETURN_FLOAT8(result);

Hm. I would let them as-is, and update your patch to do the similar checks
in the new functions introduced. See f9ac414 from 2007 which is the result
of the following thread:
/messages/by-id/200612271844.kBRIiVb18465@momjian.us
It doesn't seem wise to be backward regarding those Inf/NaN checks.

The alternative expected outputs for test float8 need to be updated,
this is causing regression failures particularly on win32 where 3
digits are used for exponentials and where tan('NaN') actually results
in an ERROR. See for example the attached regressions.diffs.

It would be nice to split the results specific to NaN and Infinity into
separate queries. The test for arctangent is one where things should be
splitted.

c5e86ea took some of the OIDs of this previous patch, so I rebased it as
attached.

        result = 1.0 / result;
-       CHECKFLOATVAL(result, true /* cotan(pi/2) == inf */ , true);
+       CHECKFLOATVAL(result, true /* cot(0) == Inf */ , true);
        PG_RETURN_FLOAT8(result);
This one is true. it could be corrected as an independent fix.
+       if (isinf(arg1) || arg1 < -1 || arg1 > 1)
+               ereport(ERROR,
+
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+                                errmsg("input is out of range")));
This should check on -1.0 and 1.0?
+       if (arg1 > 90)
+       {
+               /* tand(180-x) = -tand(x) */
+               arg1 = 180 - arg1;
+               sign = -sign;
+       }
Similarly the series of checks in atand, dcosd, asind, should use .0
precision points? Same remark for other code paths like cosd_0_to_60 for
example.
+       if (arg1 > 180)
+       {
+               /* tand(360-x) = -tand(x) */
+        arg1 = 360 - arg1;
+               sign = -sign;
+       }
Picky detail: be careful of incorrect tab spaces.

=# select acos(-1.1);
acos
------
NaN
(1 row)
=# select acosd(-1.1);
ERROR: 22003: input is out of range
LOCATION: dacosd, float.c:1752
Some results are inconsistent, it seems that we should return NaN in the
second case instead of an error.

I had as well a look at the monotony of those functions, using rough
queries like this one, and things are looking nice. The precise values are
taken into account and their surroundings are monotone.
with degrees as (
select generate_series(89.999999998, 90.000000002, 0.000000001)
union all select generate_series(44.999999998, 45.000000002, 0.000000001)
union all select generate_series(29.999999998, 30.000000002, 0.000000001)
union all select generate_series(-0.000000002, 0.000000002, 0.000000001)
union all select generate_series(59.999999998, 60.000000002, 0.000000001))
SELECT x, cosd(x), sind(x), tand(x) FROM degrees as deg(x);
with degrees as (
select generate_series((sqrt(3) / 3 - 0.00001)::numeric, (sqrt(3) / 3 +
0.00001)::numeric, 0.000001)
union all select generate_series((sqrt(3) / 2 - 0.00001)::numeric, (sqrt(3)
/ 2 + 0.00001)::numeric, 0.000001)
union all select generate_series(0.5 - 0.00001, 0.5 + 0.00001, 0.000001)
union all select generate_series(0, 0.00001, 0.000001)
union all select generate_series(0.99999, 1, 0.000001))
select x, acosd(x), asind(x), atand(x) from degrees as deg(x);
Attached are the results of all those things if others want to have a look.
Regards,
--
Michael

Attachments:

degrees.sqlapplication/octet-stream; name=degrees.sqlDownload
20151111_trigd_v2.patchtext/x-patch; charset=US-ASCII; name=20151111_trigd_v2.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 60b9a09..3716210 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -993,9 +993,9 @@
    Finally, <xref linkend="functions-math-trig-table"> shows the
    available trigonometric functions.  All trigonometric functions
    take arguments and return values of type <type>double
-   precision</type>. Trigonometric functions arguments are expressed
-   in radians. Inverse functions return values are expressed in
-   radians.  See unit transformation functions
+   precision</type>.  Each of the trigonometric functions comes in
+   two varieties, one which works in radians and one which works in
+   degrees.  See unit transformation functions
    <literal><function>radians()</function></literal> and
    <literal><function>degrees()</function></literal> above.
   </para>
@@ -1003,10 +1003,11 @@
    <table id="functions-math-trig-table">
     <title>Trigonometric Functions</title>
 
-    <tgroup cols="2">
+    <tgroup cols="3">
      <thead>
       <row>
-       <entry>Function</entry>
+       <entry>Function (radians)</entry>
+       <entry>Function (degrees)</entry>
        <entry>Description</entry>
       </row>
      </thead>
@@ -1018,6 +1019,11 @@
          <primary>acos</primary>
         </indexterm><literal><function>acos(<replaceable>x</replaceable>)</function></literal>
        </entry>
+       <entry>
+        <indexterm>
+         <primary>acosd</primary>
+        </indexterm><literal><function>acosd(<replaceable>x</replaceable>)</function></literal>
+       </entry>
        <entry>inverse cosine</entry>
       </row>
 
@@ -1028,6 +1034,12 @@
         </indexterm>
         <literal><function>asin(<replaceable>x</replaceable>)</function></literal>
        </entry>
+       <entry>
+        <indexterm>
+         <primary>asind</primary>
+        </indexterm>
+        <literal><function>asind(<replaceable>x</replaceable>)</function></literal>
+       </entry>
        <entry>inverse sine</entry>
       </row>
 
@@ -1038,6 +1050,12 @@
         </indexterm>
         <literal><function>atan(<replaceable>x</replaceable>)</function></literal>
        </entry>
+       <entry>
+        <indexterm>
+         <primary>atand</primary>
+        </indexterm>
+        <literal><function>atand(<replaceable>x</replaceable>)</function></literal>
+       </entry>
        <entry>inverse tangent</entry>
       </row>
 
@@ -1049,6 +1067,13 @@
         <literal><function>atan2(<replaceable>y</replaceable>,
         <replaceable>x</replaceable>)</function></literal>
        </entry>
+       <entry>
+        <indexterm>
+         <primary>atan2d</primary>
+        </indexterm>
+        <literal><function>atan2d(<replaceable>y</replaceable>,
+        <replaceable>x</replaceable>)</function></literal>
+       </entry>
        <entry>inverse tangent of
         <literal><replaceable>y</replaceable>/<replaceable>x</replaceable></literal></entry>
       </row>
@@ -1060,6 +1085,12 @@
         </indexterm>
         <literal><function>cos(<replaceable>x</replaceable>)</function></literal>
        </entry>
+       <entry>
+        <indexterm>
+         <primary>cosd</primary>
+        </indexterm>
+        <literal><function>cosd(<replaceable>x</replaceable>)</function></literal>
+       </entry>
        <entry>cosine</entry>
       </row>
 
@@ -1070,6 +1101,12 @@
         </indexterm>
         <literal><function>cot(<replaceable>x</replaceable>)</function></literal>
        </entry>
+       <entry>
+        <indexterm>
+         <primary>cotd</primary>
+        </indexterm>
+        <literal><function>cotd(<replaceable>x</replaceable>)</function></literal>
+       </entry>
        <entry>cotangent</entry>
       </row>
 
@@ -1080,6 +1117,12 @@
         </indexterm>
         <literal><function>sin(<replaceable>x</replaceable>)</function></literal>
        </entry>
+       <entry>
+        <indexterm>
+         <primary>sind</primary>
+        </indexterm>
+        <literal><function>sind(<replaceable>x</replaceable>)</function></literal>
+       </entry>
        <entry>sine</entry>
       </row>
 
@@ -1090,6 +1133,12 @@
         </indexterm>
         <literal><function>tan(<replaceable>x</replaceable>)</function></literal>
        </entry>
+       <entry>
+        <indexterm>
+         <primary>tand</primary>
+        </indexterm>
+        <literal><function>tand(<replaceable>x</replaceable>)</function></literal>
+       </entry>
        <entry>tangent</entry>
       </row>
      </tbody>
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 4e927d8..d2318f7 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -1535,7 +1535,7 @@ dacos(PG_FUNCTION_ARGS)
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("input is out of range")));
 
-	CHECKFLOATVAL(result, isinf(arg1), true);
+	CHECKFLOATVAL(result, false, true);
 	PG_RETURN_FLOAT8(result);
 }
 
@@ -1556,7 +1556,7 @@ dasin(PG_FUNCTION_ARGS)
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("input is out of range")));
 
-	CHECKFLOATVAL(result, isinf(arg1), true);
+	CHECKFLOATVAL(result, false, true);
 	PG_RETURN_FLOAT8(result);
 }
 
@@ -1577,7 +1577,7 @@ datan(PG_FUNCTION_ARGS)
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("input is out of range")));
 
-	CHECKFLOATVAL(result, isinf(arg1), true);
+	CHECKFLOATVAL(result, false, true);
 	PG_RETURN_FLOAT8(result);
 }
 
@@ -1599,7 +1599,7 @@ datan2(PG_FUNCTION_ARGS)
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("input is out of range")));
 
-	CHECKFLOATVAL(result, isinf(arg1) || isinf(arg2), true);
+	CHECKFLOATVAL(result, false, true);
 	PG_RETURN_FLOAT8(result);
 }
 
@@ -1620,7 +1620,7 @@ dcos(PG_FUNCTION_ARGS)
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("input is out of range")));
 
-	CHECKFLOATVAL(result, isinf(arg1), true);
+	CHECKFLOATVAL(result, false, true);
 	PG_RETURN_FLOAT8(result);
 }
 
@@ -1642,7 +1642,7 @@ dcot(PG_FUNCTION_ARGS)
 				 errmsg("input is out of range")));
 
 	result = 1.0 / result;
-	CHECKFLOATVAL(result, true /* cotan(pi/2) == inf */ , true);
+	CHECKFLOATVAL(result, true /* cot(0) == Inf */ , true);
 	PG_RETURN_FLOAT8(result);
 }
 
@@ -1663,7 +1663,7 @@ dsin(PG_FUNCTION_ARGS)
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("input is out of range")));
 
-	CHECKFLOATVAL(result, isinf(arg1), true);
+	CHECKFLOATVAL(result, false, true);
 	PG_RETURN_FLOAT8(result);
 }
 
@@ -1690,6 +1690,368 @@ dtan(PG_FUNCTION_ARGS)
 
 
 /*
+ *		asind_q1		- returns the inverse sine of x in degrees, where x is
+ *						  assumed to be in the range [0, 1] and the result is
+ *						  an angle in the first quadrant (0 to 90 degrees).
+ *
+ * In this quadrant there are 3 special case inputs (0, 0.5 and 1) for which
+ * this function will return exact values (0, 30 and 90 respectively).
+ */
+static double
+asind_q1(double x)
+{
+	/*
+	 * Stitch together inverse sine and cosine functions for the ranges
+	 * [0, 0.5] and [0.5, 1].  Each expression below is guaranteed to return
+	 * exactly 30 for x=0.5, so the result is a continuous monotonic function
+	 * over the full range.
+	 */
+	if (x <= 0.5)
+		return (asin(x) / asin(0.5)) * 30;
+	else
+		return 90 - (acos(x) / acos(0.5)) * 60;
+}
+
+
+/*
+ *		acosd_q1		- returns the inverse cosine of x in degrees, where x
+ *						  is assumed to be in the range [0, 1] and the result
+ *						  is an angle in the first quadrant (0 to 90 degrees).
+ *
+ * In this quadrant there are 3 special case inputs (0, 0.5 and 1) for which
+ * this function will return exact values (0, 60 and 90 respectively).
+ */
+static double
+acosd_q1(double x)
+{
+	/*
+	 * Stitch together inverse sine and cosine functions for the ranges
+	 * [0, 0.5] and [0.5, 1].  Each expression below is guaranteed to return
+	 * exactly 60 for x=0.5, so the result is a continuous monotonic function
+	 * over the full range.
+	 */
+	if (x <= 0.5)
+		return 90 - (asin(x) / asin(0.5)) * 30;
+	else
+		return (acos(x) / acos(0.5)) * 60;
+}
+
+
+/*
+ *		dacosd			- returns the arccos of arg1 (degrees)
+ */
+Datum
+dacosd(PG_FUNCTION_ARGS)
+{
+	float8		arg1 = PG_GETARG_FLOAT8(0);
+	float8		result;
+
+	if (isinf(arg1) || arg1 < -1 || arg1 > 1)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("input is out of range")));
+
+	if (isnan(arg1))
+		result = arg1;
+	else if (arg1 >= 0)
+		result = acosd_q1(arg1);
+	else
+		result = 90 + asind_q1(-arg1);
+
+	CHECKFLOATVAL(result, false, true);
+	PG_RETURN_FLOAT8(result);
+}
+
+
+/*
+ *		dasind			- returns the arcsin of arg1 (degrees)
+ */
+Datum
+dasind(PG_FUNCTION_ARGS)
+{
+	float8		arg1 = PG_GETARG_FLOAT8(0);
+	float8		result;
+
+	if (isinf(arg1) || arg1 < -1 || arg1 > 1)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("input is out of range")));
+
+	if (isnan(arg1))
+		result = arg1;
+	else if (arg1 >= 0)
+		result = asind_q1(arg1);
+	else
+		result = -asind_q1(-arg1);
+
+	CHECKFLOATVAL(result, false, true);
+	PG_RETURN_FLOAT8(result);
+}
+
+
+/*
+ *		datand			- returns the arctan of arg1 (degrees)
+ */
+Datum
+datand(PG_FUNCTION_ARGS)
+{
+	float8		arg1 = PG_GETARG_FLOAT8(0);
+	float8		result;
+
+	errno = 0;
+	result = atan(arg1);
+	if (errno != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("input is out of range")));
+
+	result = (result / atan(1.0)) * 45;
+	CHECKFLOATVAL(result, false, true);
+	PG_RETURN_FLOAT8(result);
+}
+
+
+/*
+ *		atan2d			- returns the arctan2 of arg1 (degrees)
+ */
+Datum
+datan2d(PG_FUNCTION_ARGS)
+{
+	float8		arg1 = PG_GETARG_FLOAT8(0);
+	float8		arg2 = PG_GETARG_FLOAT8(1);
+	float8		result;
+
+	errno = 0;
+	result = atan2(arg1, arg2);
+	if (errno != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("input is out of range")));
+
+	result = (result / atan(1.0)) * 45;
+	CHECKFLOATVAL(result, false, true);
+	PG_RETURN_FLOAT8(result);
+}
+
+
+/*
+ *		sind_0_to_30	- returns the sine of an angle that lies between 0 and
+ *						  30 degrees.  This will return exactly 0 when x is 0,
+ *						  and exactly 0.5 when x is 30 degrees.
+ */
+static double
+sind_0_to_30(double x)
+{
+	return ( sin(x * (M_PI/180)) / sin(30 * (M_PI/180)) ) / 2;
+}
+
+
+/*
+ *		cosd_0_to_60	- returns the cosine of an angle that lies between 0
+ *						  and 60 degrees.  This will return exactly 1 when x
+ *						  is 0 and exactly 0.5 when x is 60 degrees.
+ */
+static double
+cosd_0_to_60(double x)
+{
+	return ( 2 - (1 - cos(x * (M_PI/180))) / (1 - cos(60 * (M_PI/180))) ) / 2;
+}
+
+
+/*
+ *		sind_q1			- returns the sine of an angle in the first quadrant
+ *						  (0 to 90 degrees).
+ */
+static double
+sind_q1(double x)
+{
+	/*
+	 * Stitch together the sine and cosine functions for the ranges [0, 30]
+	 * and [30, 90].  These guarantee to return exact answers at their
+	 * endpoints, so the overall result is a continuous monotonic function
+	 * that gives exact results when x = 0, 30 and 90 degrees.
+	 */
+	return x <= 30 ? sind_0_to_30(x) : cosd_0_to_60(90-x);
+}
+
+
+/*
+ *		cosd_q1			- returns the cosine of an angle in the first quadrant
+ *						  (0 to 90 degrees).
+ */
+static double
+cosd_q1(double x)
+{
+	/*
+	 * Stitch together the sine and cosine functions for the ranges [0, 60]
+	 * and [60, 90].  These guarantee to return exact answers at their
+	 * endpoints, so the overall result is a continuous monotonic function
+	 * that gives exact results when x = 0, 60 and 90 degrees.
+	 */
+	return x <= 60 ? cosd_0_to_60(x) : sind_0_to_30(90-x);
+}
+
+
+/*
+ *		dcosd			- returns the cosine of arg1 (degrees)
+ */
+Datum
+dcosd(PG_FUNCTION_ARGS)
+{
+	float8		arg1 = PG_GETARG_FLOAT8(0);
+	int			sign = 1;
+	float8		result;
+
+	/* Reduce the range of the input to [0,90] degrees */
+	arg1 = fmod(arg1, 360.0);
+	if (isnan(arg1))
+		PG_RETURN_FLOAT8(arg1);
+
+	if (arg1 < 0)
+		/* cosd(-x) = cosd(x) */
+		arg1 = -arg1;
+
+	if (arg1 > 180)
+		/* cosd(360-x) = cosd(x) */
+		arg1 = 360 - arg1;
+
+	if (arg1 > 90)
+	{
+		/* cosd(180-x) = -cosd(x) */
+		arg1 = 180 - arg1;
+		sign = -sign;
+	}
+
+	result = sign * cosd_q1(arg1);
+	CHECKFLOATVAL(result, false, true);
+	PG_RETURN_FLOAT8(result);
+}
+
+
+/*
+ *		dcotd			- returns the cotangent of arg1 (degrees)
+ */
+Datum
+dcotd(PG_FUNCTION_ARGS)
+{
+	float8		arg1 = PG_GETARG_FLOAT8(0);
+	int			sign = 1;
+	float8		result;
+
+	/* Reduce the range of the input to [0,90] degrees */
+	arg1 = fmod(arg1, 360.0);
+	if (isnan(arg1))
+		PG_RETURN_FLOAT8(arg1);
+
+	if (arg1 < 0)
+	{
+		/* cotd(-x) = -cotd(x) */
+		arg1 = -arg1;
+		sign = -sign;
+	}
+
+	if (arg1 > 180)
+	{
+		/* cotd(360-x) = -cotd(x) */
+		arg1 = 360 - arg1;
+		sign = -sign;
+	}
+
+	if (arg1 > 90)
+	{
+		/* cotd(180-x) = -cotd(x) */
+		arg1 = 180 - arg1;
+		sign = -sign;
+	}
+
+	result = sign * cosd_q1(arg1) / sind_q1(arg1);
+	CHECKFLOATVAL(result, true /* cotd(0) == Inf */ , true);
+	PG_RETURN_FLOAT8(result);
+}
+
+
+/*
+ *		dsind			- returns the sine of arg1 (degrees)
+ */
+Datum
+dsind(PG_FUNCTION_ARGS)
+{
+	float8		arg1 = PG_GETARG_FLOAT8(0);
+	int			sign = 1;
+	float8		result;
+
+	/* Reduce the range of the input to [0,90] degrees */
+	arg1 = fmod(arg1, 360.0);
+	if (isnan(arg1))
+		PG_RETURN_FLOAT8(arg1);
+
+	if (arg1 < 0)
+	{
+		/* sind(-x) = -sind(x) */
+		arg1 = -arg1;
+		sign = -sign;
+	}
+
+	if (arg1 > 180)
+	{
+		/* sind(360-x) = -sind(x) */
+		arg1 = 360 - arg1;
+		sign = -sign;
+	}
+
+	if (arg1 > 90)
+		/* sind(180-x) = sind(x) */
+		arg1 = 180 - arg1;
+
+	result = sign * sind_q1(arg1);
+	CHECKFLOATVAL(result, false, true);
+	PG_RETURN_FLOAT8(result);
+}
+
+
+/*
+ *		dtand			- returns the tangent of arg1 (degrees)
+ */
+Datum
+dtand(PG_FUNCTION_ARGS)
+{
+	float8		arg1 = PG_GETARG_FLOAT8(0);
+	int			sign = 1;
+	float8		result;
+
+	/* Reduce the range of the input to [0,90] degrees */
+	arg1 = fmod(arg1, 360.0);
+	if (isnan(arg1))
+		PG_RETURN_FLOAT8(arg1);
+
+	if (arg1 < 0)
+	{
+		/* tand(-x) = -tand(x) */
+		arg1 = -arg1;
+		sign = -sign;
+	}
+
+	if (arg1 > 180)
+	{
+		/* tand(360-x) = -tand(x) */
+        arg1 = 360 - arg1;
+		sign = -sign;
+	}
+
+	if (arg1 > 90)
+	{
+		/* tand(180-x) = -tand(x) */
+		arg1 = 180 - arg1;
+		sign = -sign;
+	}
+
+	result = sign * sind_q1(arg1) / cosd_q1(arg1);
+	CHECKFLOATVAL(result, true /* tand(90) == Inf */ , true);
+	PG_RETURN_FLOAT8(result);
+}
+
+
+/*
  *		degrees		- returns degrees converted from radians
  */
 Datum
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index d8640db..f4d468e 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1894,6 +1894,24 @@ DATA(insert OID = 1606 (  tan				PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701
 DESCR("tangent");
 DATA(insert OID = 1607 (  cot				PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701 "701" _null_ _null_ _null_ _null_ _null_ dcot _null_ _null_ _null_ ));
 DESCR("cotangent");
+
+DATA(insert OID = 3317 (  asind				PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701 "701" _null_ _null_ _null_ _null_ _null_ dasind _null_ _null_ _null_ ));
+DESCR("arcsine, degrees");
+DATA(insert OID = 3318 (  acosd				PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701 "701" _null_ _null_ _null_ _null_ _null_ dacosd _null_ _null_ _null_ ));
+DESCR("arccosine, degrees");
+DATA(insert OID = 3319 (  atand				PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701 "701" _null_ _null_ _null_ _null_ _null_ datand _null_ _null_ _null_ ));
+DESCR("arctangent, degrees");
+DATA(insert OID = 3320 (  atan2d			PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 701 "701 701" _null_ _null_ _null_ _null_ _null_ datan2d _null_ _null_ _null_ ));
+DESCR("arctangent, two arguments, degrees");
+DATA(insert OID = 3321 (  sind				PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701 "701" _null_ _null_ _null_ _null_ _null_ dsind _null_ _null_ _null_ ));
+DESCR("sine, degrees");
+DATA(insert OID = 3322 (  cosd				PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701 "701" _null_ _null_ _null_ _null_ _null_ dcosd _null_ _null_ _null_ ));
+DESCR("cosine, degrees");
+DATA(insert OID = 3323 (  tand				PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701 "701" _null_ _null_ _null_ _null_ _null_ dtand _null_ _null_ _null_ ));
+DESCR("tangent, degrees");
+DATA(insert OID = 3324 (  cotd				PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701 "701" _null_ _null_ _null_ _null_ _null_ dcotd _null_ _null_ _null_ ));
+DESCR("cotangent, degrees");
+
 DATA(insert OID = 1608 (  degrees			PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701 "701" _null_ _null_ _null_ _null_ _null_ degrees _null_ _null_ _null_ ));
 DESCR("radians to degrees");
 DATA(insert OID = 1609 (  radians			PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701 "701" _null_ _null_ _null_ _null_ _null_ radians _null_ _null_ _null_ ));
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index e610bf3..cbca7f3 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -407,6 +407,14 @@ extern Datum dcos(PG_FUNCTION_ARGS);
 extern Datum dcot(PG_FUNCTION_ARGS);
 extern Datum dsin(PG_FUNCTION_ARGS);
 extern Datum dtan(PG_FUNCTION_ARGS);
+extern Datum dacosd(PG_FUNCTION_ARGS);
+extern Datum dasind(PG_FUNCTION_ARGS);
+extern Datum datand(PG_FUNCTION_ARGS);
+extern Datum datan2d(PG_FUNCTION_ARGS);
+extern Datum dcosd(PG_FUNCTION_ARGS);
+extern Datum dcotd(PG_FUNCTION_ARGS);
+extern Datum dsind(PG_FUNCTION_ARGS);
+extern Datum dtand(PG_FUNCTION_ARGS);
 extern Datum degrees(PG_FUNCTION_ARGS);
 extern Datum dpi(PG_FUNCTION_ARGS);
 extern Datum radians(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/float8.out b/src/test/regress/expected/float8.out
index 6221538..a6261d2 100644
--- a/src/test/regress/expected/float8.out
+++ b/src/test/regress/expected/float8.out
@@ -444,3 +444,330 @@ SELECT '' AS five, * FROM FLOAT8_TBL;
       | -1.2345678901234e-200
 (5 rows)
 
+-- Test the trigonometric functions.
+-- Use a lower precision to eliminate variations between platforms.
+SET extra_float_digits TO -5;
+-- sine and cosine
+SELECT x,
+       sin(radians(x)) AS sin_radians, sind(x),
+       cos(radians(x)) AS cos_radians, cosd(x)
+FROM generate_series(-360, 360, 10) AS t(x)
+WHERE x = 0 OR x % 90 != 0; -- skip tests that are very platform-dependent
+  x   |  sin_radians  |     sind      |  cos_radians  |     cosd      
+------+---------------+---------------+---------------+---------------
+ -350 |  0.1736481777 |  0.1736481777 |   0.984807753 |   0.984807753
+ -340 |  0.3420201433 |  0.3420201433 |  0.9396926208 |  0.9396926208
+ -330 |           0.5 |           0.5 |  0.8660254038 |  0.8660254038
+ -320 |  0.6427876097 |  0.6427876097 |  0.7660444431 |  0.7660444431
+ -310 |  0.7660444431 |  0.7660444431 |  0.6427876097 |  0.6427876097
+ -300 |  0.8660254038 |  0.8660254038 |           0.5 |           0.5
+ -290 |  0.9396926208 |  0.9396926208 |  0.3420201433 |  0.3420201433
+ -280 |   0.984807753 |   0.984807753 |  0.1736481777 |  0.1736481777
+ -260 |   0.984807753 |   0.984807753 | -0.1736481777 | -0.1736481777
+ -250 |  0.9396926208 |  0.9396926208 | -0.3420201433 | -0.3420201433
+ -240 |  0.8660254038 |  0.8660254038 |          -0.5 |          -0.5
+ -230 |  0.7660444431 |  0.7660444431 | -0.6427876097 | -0.6427876097
+ -220 |  0.6427876097 |  0.6427876097 | -0.7660444431 | -0.7660444431
+ -210 |           0.5 |           0.5 | -0.8660254038 | -0.8660254038
+ -200 |  0.3420201433 |  0.3420201433 | -0.9396926208 | -0.9396926208
+ -190 |  0.1736481777 |  0.1736481777 |  -0.984807753 |  -0.984807753
+ -170 | -0.1736481777 | -0.1736481777 |  -0.984807753 |  -0.984807753
+ -160 | -0.3420201433 | -0.3420201433 | -0.9396926208 | -0.9396926208
+ -150 |          -0.5 |          -0.5 | -0.8660254038 | -0.8660254038
+ -140 | -0.6427876097 | -0.6427876097 | -0.7660444431 | -0.7660444431
+ -130 | -0.7660444431 | -0.7660444431 | -0.6427876097 | -0.6427876097
+ -120 | -0.8660254038 | -0.8660254038 |          -0.5 |          -0.5
+ -110 | -0.9396926208 | -0.9396926208 | -0.3420201433 | -0.3420201433
+ -100 |  -0.984807753 |  -0.984807753 | -0.1736481777 | -0.1736481777
+  -80 |  -0.984807753 |  -0.984807753 |  0.1736481777 |  0.1736481777
+  -70 | -0.9396926208 | -0.9396926208 |  0.3420201433 |  0.3420201433
+  -60 | -0.8660254038 | -0.8660254038 |           0.5 |           0.5
+  -50 | -0.7660444431 | -0.7660444431 |  0.6427876097 |  0.6427876097
+  -40 | -0.6427876097 | -0.6427876097 |  0.7660444431 |  0.7660444431
+  -30 |          -0.5 |          -0.5 |  0.8660254038 |  0.8660254038
+  -20 | -0.3420201433 | -0.3420201433 |  0.9396926208 |  0.9396926208
+  -10 | -0.1736481777 | -0.1736481777 |   0.984807753 |   0.984807753
+    0 |             0 |             0 |             1 |             1
+   10 |  0.1736481777 |  0.1736481777 |   0.984807753 |   0.984807753
+   20 |  0.3420201433 |  0.3420201433 |  0.9396926208 |  0.9396926208
+   30 |           0.5 |           0.5 |  0.8660254038 |  0.8660254038
+   40 |  0.6427876097 |  0.6427876097 |  0.7660444431 |  0.7660444431
+   50 |  0.7660444431 |  0.7660444431 |  0.6427876097 |  0.6427876097
+   60 |  0.8660254038 |  0.8660254038 |           0.5 |           0.5
+   70 |  0.9396926208 |  0.9396926208 |  0.3420201433 |  0.3420201433
+   80 |   0.984807753 |   0.984807753 |  0.1736481777 |  0.1736481777
+  100 |   0.984807753 |   0.984807753 | -0.1736481777 | -0.1736481777
+  110 |  0.9396926208 |  0.9396926208 | -0.3420201433 | -0.3420201433
+  120 |  0.8660254038 |  0.8660254038 |          -0.5 |          -0.5
+  130 |  0.7660444431 |  0.7660444431 | -0.6427876097 | -0.6427876097
+  140 |  0.6427876097 |  0.6427876097 | -0.7660444431 | -0.7660444431
+  150 |           0.5 |           0.5 | -0.8660254038 | -0.8660254038
+  160 |  0.3420201433 |  0.3420201433 | -0.9396926208 | -0.9396926208
+  170 |  0.1736481777 |  0.1736481777 |  -0.984807753 |  -0.984807753
+  190 | -0.1736481777 | -0.1736481777 |  -0.984807753 |  -0.984807753
+  200 | -0.3420201433 | -0.3420201433 | -0.9396926208 | -0.9396926208
+  210 |          -0.5 |          -0.5 | -0.8660254038 | -0.8660254038
+  220 | -0.6427876097 | -0.6427876097 | -0.7660444431 | -0.7660444431
+  230 | -0.7660444431 | -0.7660444431 | -0.6427876097 | -0.6427876097
+  240 | -0.8660254038 | -0.8660254038 |          -0.5 |          -0.5
+  250 | -0.9396926208 | -0.9396926208 | -0.3420201433 | -0.3420201433
+  260 |  -0.984807753 |  -0.984807753 | -0.1736481777 | -0.1736481777
+  280 |  -0.984807753 |  -0.984807753 |  0.1736481777 |  0.1736481777
+  290 | -0.9396926208 | -0.9396926208 |  0.3420201433 |  0.3420201433
+  300 | -0.8660254038 | -0.8660254038 |           0.5 |           0.5
+  310 | -0.7660444431 | -0.7660444431 |  0.6427876097 |  0.6427876097
+  320 | -0.6427876097 | -0.6427876097 |  0.7660444431 |  0.7660444431
+  330 |          -0.5 |          -0.5 |  0.8660254038 |  0.8660254038
+  340 | -0.3420201433 | -0.3420201433 |  0.9396926208 |  0.9396926208
+  350 | -0.1736481777 | -0.1736481777 |   0.984807753 |   0.984807753
+(65 rows)
+
+-- test exactness
+SELECT x,
+       CASE WHEN sind(x) IN (-1,-0.5,0,0.5,1) THEN sind(x) END AS sind,
+       CASE WHEN cosd(x) IN (-1,-0.5,0,0.5,1) THEN cosd(x) END AS cosd
+FROM generate_series(0, 360, 30) AS t(x);
+  x  | sind | cosd 
+-----+------+------
+   0 |    0 |    1
+  30 |  0.5 |     
+  60 |      |  0.5
+  90 |    1 |    0
+ 120 |      | -0.5
+ 150 |  0.5 |     
+ 180 |    0 |   -1
+ 210 | -0.5 |     
+ 240 |      | -0.5
+ 270 |   -1 |    0
+ 300 |      |  0.5
+ 330 | -0.5 |     
+ 360 |    0 |    1
+(13 rows)
+
+-- tangent and cotangent
+SELECT x,
+       tan(radians(x)) AS tan_radians, tand(x),
+       cot(radians(x)) AS cot_radians, cotd(x)
+FROM generate_series(-360, 360, 10) AS t(x)
+WHERE x = 0 OR x % 90 != 0; -- skip tests that are very platform-dependent
+  x   |  tan_radians  |     tand      |  cot_radians  |     cotd      
+------+---------------+---------------+---------------+---------------
+ -350 |  0.1763269807 |  0.1763269807 |    5.67128182 |    5.67128182
+ -340 |  0.3639702343 |  0.3639702343 |   2.747477419 |   2.747477419
+ -330 |  0.5773502692 |  0.5773502692 |   1.732050808 |   1.732050808
+ -320 |  0.8390996312 |  0.8390996312 |   1.191753593 |   1.191753593
+ -310 |   1.191753593 |   1.191753593 |  0.8390996312 |  0.8390996312
+ -300 |   1.732050808 |   1.732050808 |  0.5773502692 |  0.5773502692
+ -290 |   2.747477419 |   2.747477419 |  0.3639702343 |  0.3639702343
+ -280 |    5.67128182 |    5.67128182 |  0.1763269807 |  0.1763269807
+ -260 |   -5.67128182 |   -5.67128182 | -0.1763269807 | -0.1763269807
+ -250 |  -2.747477419 |  -2.747477419 | -0.3639702343 | -0.3639702343
+ -240 |  -1.732050808 |  -1.732050808 | -0.5773502692 | -0.5773502692
+ -230 |  -1.191753593 |  -1.191753593 | -0.8390996312 | -0.8390996312
+ -220 | -0.8390996312 | -0.8390996312 |  -1.191753593 |  -1.191753593
+ -210 | -0.5773502692 | -0.5773502692 |  -1.732050808 |  -1.732050808
+ -200 | -0.3639702343 | -0.3639702343 |  -2.747477419 |  -2.747477419
+ -190 | -0.1763269807 | -0.1763269807 |   -5.67128182 |   -5.67128182
+ -170 |  0.1763269807 |  0.1763269807 |    5.67128182 |    5.67128182
+ -160 |  0.3639702343 |  0.3639702343 |   2.747477419 |   2.747477419
+ -150 |  0.5773502692 |  0.5773502692 |   1.732050808 |   1.732050808
+ -140 |  0.8390996312 |  0.8390996312 |   1.191753593 |   1.191753593
+ -130 |   1.191753593 |   1.191753593 |  0.8390996312 |  0.8390996312
+ -120 |   1.732050808 |   1.732050808 |  0.5773502692 |  0.5773502692
+ -110 |   2.747477419 |   2.747477419 |  0.3639702343 |  0.3639702343
+ -100 |    5.67128182 |    5.67128182 |  0.1763269807 |  0.1763269807
+  -80 |   -5.67128182 |   -5.67128182 | -0.1763269807 | -0.1763269807
+  -70 |  -2.747477419 |  -2.747477419 | -0.3639702343 | -0.3639702343
+  -60 |  -1.732050808 |  -1.732050808 | -0.5773502692 | -0.5773502692
+  -50 |  -1.191753593 |  -1.191753593 | -0.8390996312 | -0.8390996312
+  -40 | -0.8390996312 | -0.8390996312 |  -1.191753593 |  -1.191753593
+  -30 | -0.5773502692 | -0.5773502692 |  -1.732050808 |  -1.732050808
+  -20 | -0.3639702343 | -0.3639702343 |  -2.747477419 |  -2.747477419
+  -10 | -0.1763269807 | -0.1763269807 |   -5.67128182 |   -5.67128182
+    0 |             0 |             0 |      Infinity |      Infinity
+   10 |  0.1763269807 |  0.1763269807 |    5.67128182 |    5.67128182
+   20 |  0.3639702343 |  0.3639702343 |   2.747477419 |   2.747477419
+   30 |  0.5773502692 |  0.5773502692 |   1.732050808 |   1.732050808
+   40 |  0.8390996312 |  0.8390996312 |   1.191753593 |   1.191753593
+   50 |   1.191753593 |   1.191753593 |  0.8390996312 |  0.8390996312
+   60 |   1.732050808 |   1.732050808 |  0.5773502692 |  0.5773502692
+   70 |   2.747477419 |   2.747477419 |  0.3639702343 |  0.3639702343
+   80 |    5.67128182 |    5.67128182 |  0.1763269807 |  0.1763269807
+  100 |   -5.67128182 |   -5.67128182 | -0.1763269807 | -0.1763269807
+  110 |  -2.747477419 |  -2.747477419 | -0.3639702343 | -0.3639702343
+  120 |  -1.732050808 |  -1.732050808 | -0.5773502692 | -0.5773502692
+  130 |  -1.191753593 |  -1.191753593 | -0.8390996312 | -0.8390996312
+  140 | -0.8390996312 | -0.8390996312 |  -1.191753593 |  -1.191753593
+  150 | -0.5773502692 | -0.5773502692 |  -1.732050808 |  -1.732050808
+  160 | -0.3639702343 | -0.3639702343 |  -2.747477419 |  -2.747477419
+  170 | -0.1763269807 | -0.1763269807 |   -5.67128182 |   -5.67128182
+  190 |  0.1763269807 |  0.1763269807 |    5.67128182 |    5.67128182
+  200 |  0.3639702343 |  0.3639702343 |   2.747477419 |   2.747477419
+  210 |  0.5773502692 |  0.5773502692 |   1.732050808 |   1.732050808
+  220 |  0.8390996312 |  0.8390996312 |   1.191753593 |   1.191753593
+  230 |   1.191753593 |   1.191753593 |  0.8390996312 |  0.8390996312
+  240 |   1.732050808 |   1.732050808 |  0.5773502692 |  0.5773502692
+  250 |   2.747477419 |   2.747477419 |  0.3639702343 |  0.3639702343
+  260 |    5.67128182 |    5.67128182 |  0.1763269807 |  0.1763269807
+  280 |   -5.67128182 |   -5.67128182 | -0.1763269807 | -0.1763269807
+  290 |  -2.747477419 |  -2.747477419 | -0.3639702343 | -0.3639702343
+  300 |  -1.732050808 |  -1.732050808 | -0.5773502692 | -0.5773502692
+  310 |  -1.191753593 |  -1.191753593 | -0.8390996312 | -0.8390996312
+  320 | -0.8390996312 | -0.8390996312 |  -1.191753593 |  -1.191753593
+  330 | -0.5773502692 | -0.5773502692 |  -1.732050808 |  -1.732050808
+  340 | -0.3639702343 | -0.3639702343 |  -2.747477419 |  -2.747477419
+  350 | -0.1763269807 | -0.1763269807 |   -5.67128182 |   -5.67128182
+(65 rows)
+
+-- test exactness
+SELECT x,
+       CASE WHEN tand(x) IN (-1,0,1) THEN tand(x) END AS tand,
+       CASE WHEN cotd(x) IN (-1,0,1) THEN cotd(x) END AS cotd
+FROM generate_series(0, 360, 45) AS t(x);
+  x  | tand | cotd 
+-----+------+------
+   0 |    0 |     
+  45 |    1 |    1
+  90 |      |    0
+ 135 |   -1 |   -1
+ 180 |   -0 |     
+ 225 |    1 |    1
+ 270 |      |   -0
+ 315 |   -1 |   -1
+ 360 |    0 |     
+(9 rows)
+
+-- arcsine and arccosine
+SELECT x,
+       degrees(asin(x)) AS degrees_asin, asind(x),
+       degrees(acos(x)) AS degrees_acos, acosd(x)
+FROM generate_series(-1.0, 1.0, 0.1) AS t(x);
+  x   | degrees_asin |    asind     | degrees_acos |    acosd    
+------+--------------+--------------+--------------+-------------
+ -1.0 |          -90 |          -90 |          180 |         180
+ -0.9 | -64.15806724 | -64.15806724 |  154.1580672 | 154.1580672
+ -0.8 | -53.13010235 | -53.13010235 |  143.1301024 | 143.1301024
+ -0.7 |   -44.427004 |   -44.427004 |   134.427004 |  134.427004
+ -0.6 | -36.86989765 | -36.86989765 |  126.8698976 | 126.8698976
+ -0.5 |          -30 |          -30 |          120 |         120
+ -0.4 | -23.57817848 | -23.57817848 |  113.5781785 | 113.5781785
+ -0.3 | -17.45760312 | -17.45760312 |  107.4576031 | 107.4576031
+ -0.2 | -11.53695903 | -11.53695903 |   101.536959 |  101.536959
+ -0.1 | -5.739170477 | -5.739170477 |  95.73917048 | 95.73917048
+  0.0 |            0 |            0 |           90 |          90
+  0.1 |  5.739170477 |  5.739170477 |  84.26082952 | 84.26082952
+  0.2 |  11.53695903 |  11.53695903 |  78.46304097 | 78.46304097
+  0.3 |  17.45760312 |  17.45760312 |  72.54239688 | 72.54239688
+  0.4 |  23.57817848 |  23.57817848 |  66.42182152 | 66.42182152
+  0.5 |           30 |           30 |           60 |          60
+  0.6 |  36.86989765 |  36.86989765 |  53.13010235 | 53.13010235
+  0.7 |    44.427004 |    44.427004 |    45.572996 |   45.572996
+  0.8 |  53.13010235 |  53.13010235 |  36.86989765 | 36.86989765
+  0.9 |  64.15806724 |  64.15806724 |  25.84193276 | 25.84193276
+  1.0 |           90 |           90 |            0 |           0
+(21 rows)
+
+-- arctangent
+SELECT x, degrees(atan(x)) AS degrees_atan, atand(x)
+FROM (SELECT '-Infinity'::float8
+      UNION ALL
+      SELECT generate_series(-10, -2)
+      UNION ALL
+      SELECT generate_series(-1.0, 1.0, 0.1)
+      UNION ALL
+      SELECT generate_series(2, 10)
+      UNION ALL
+      SELECT 'Infinity'::float8
+      UNION ALL
+      SELECT 'NaN'::float8) AS t(x);
+     x     | degrees_atan |    atand     
+-----------+--------------+--------------
+ -Infinity |          -90 |          -90
+       -10 | -84.28940686 | -84.28940686
+        -9 | -83.65980825 | -83.65980825
+        -8 | -82.87498365 | -82.87498365
+        -7 | -81.86989765 | -81.86989765
+        -6 | -80.53767779 | -80.53767779
+        -5 | -78.69006753 | -78.69006753
+        -4 | -75.96375653 | -75.96375653
+        -3 | -71.56505118 | -71.56505118
+        -2 | -63.43494882 | -63.43494882
+        -1 |          -45 |          -45
+      -0.9 |  -41.9872125 |  -41.9872125
+      -0.8 | -38.65980825 | -38.65980825
+      -0.7 |  -34.9920202 |  -34.9920202
+      -0.6 | -30.96375653 | -30.96375653
+      -0.5 | -26.56505118 | -26.56505118
+      -0.4 | -21.80140949 | -21.80140949
+      -0.3 | -16.69924423 | -16.69924423
+      -0.2 | -11.30993247 | -11.30993247
+      -0.1 | -5.710593137 | -5.710593137
+         0 |            0 |            0
+       0.1 |  5.710593137 |  5.710593137
+       0.2 |  11.30993247 |  11.30993247
+       0.3 |  16.69924423 |  16.69924423
+       0.4 |  21.80140949 |  21.80140949
+       0.5 |  26.56505118 |  26.56505118
+       0.6 |  30.96375653 |  30.96375653
+       0.7 |   34.9920202 |   34.9920202
+       0.8 |  38.65980825 |  38.65980825
+       0.9 |   41.9872125 |   41.9872125
+         1 |           45 |           45
+         2 |  63.43494882 |  63.43494882
+         3 |  71.56505118 |  71.56505118
+         4 |  75.96375653 |  75.96375653
+         5 |  78.69006753 |  78.69006753
+         6 |  80.53767779 |  80.53767779
+         7 |  81.86989765 |  81.86989765
+         8 |  82.87498365 |  82.87498365
+         9 |  83.65980825 |  83.65980825
+        10 |  84.28940686 |  84.28940686
+  Infinity |           90 |           90
+       NaN |          NaN |          NaN
+(42 rows)
+
+-- 4 quadrant arctangent
+SELECT x, y, degrees(atan2(y, x)) AS degrees_atan2, atan2d(y, x)
+FROM (SELECT 10*cosd(a), 10*sind(a)
+      FROM generate_series(0, 360, 10) AS t(a)) AS t(x,y);
+      x       |      y       | degrees_atan2 | atan2d 
+--------------+--------------+---------------+--------
+           10 |            0 |             0 |      0
+   9.84807753 |  1.736481777 |            10 |     10
+  9.396926208 |  3.420201433 |            20 |     20
+  8.660254038 |            5 |            30 |     30
+  7.660444431 |  6.427876097 |            40 |     40
+  6.427876097 |  7.660444431 |            50 |     50
+            5 |  8.660254038 |            60 |     60
+  3.420201433 |  9.396926208 |            70 |     70
+  1.736481777 |   9.84807753 |            80 |     80
+            0 |           10 |            90 |     90
+ -1.736481777 |   9.84807753 |           100 |    100
+ -3.420201433 |  9.396926208 |           110 |    110
+           -5 |  8.660254038 |           120 |    120
+ -6.427876097 |  7.660444431 |           130 |    130
+ -7.660444431 |  6.427876097 |           140 |    140
+ -8.660254038 |            5 |           150 |    150
+ -9.396926208 |  3.420201433 |           160 |    160
+  -9.84807753 |  1.736481777 |           170 |    170
+          -10 |            0 |           180 |    180
+  -9.84807753 | -1.736481777 |          -170 |   -170
+ -9.396926208 | -3.420201433 |          -160 |   -160
+ -8.660254038 |           -5 |          -150 |   -150
+ -7.660444431 | -6.427876097 |          -140 |   -140
+ -6.427876097 | -7.660444431 |          -130 |   -130
+           -5 | -8.660254038 |          -120 |   -120
+ -3.420201433 | -9.396926208 |          -110 |   -110
+ -1.736481777 |  -9.84807753 |          -100 |   -100
+            0 |          -10 |           -90 |    -90
+  1.736481777 |  -9.84807753 |           -80 |    -80
+  3.420201433 | -9.396926208 |           -70 |    -70
+            5 | -8.660254038 |           -60 |    -60
+  6.427876097 | -7.660444431 |           -50 |    -50
+  7.660444431 | -6.427876097 |           -40 |    -40
+  8.660254038 |           -5 |           -30 |    -30
+  9.396926208 | -3.420201433 |           -20 |    -20
+   9.84807753 | -1.736481777 |           -10 |    -10
+           10 |            0 |             0 |      0
+(37 rows)
+
diff --git a/src/test/regress/sql/float8.sql b/src/test/regress/sql/float8.sql
index 92a574a..49363d0 100644
--- a/src/test/regress/sql/float8.sql
+++ b/src/test/regress/sql/float8.sql
@@ -167,3 +167,58 @@ INSERT INTO FLOAT8_TBL(f1) VALUES ('-1.2345678901234e+200');
 INSERT INTO FLOAT8_TBL(f1) VALUES ('-1.2345678901234e-200');
 
 SELECT '' AS five, * FROM FLOAT8_TBL;
+
+-- Test the trigonometric functions.
+-- Use a lower precision to eliminate variations between platforms.
+SET extra_float_digits TO -5;
+
+-- sine and cosine
+SELECT x,
+       sin(radians(x)) AS sin_radians, sind(x),
+       cos(radians(x)) AS cos_radians, cosd(x)
+FROM generate_series(-360, 360, 10) AS t(x)
+WHERE x = 0 OR x % 90 != 0; -- skip tests that are very platform-dependent
+
+-- test exactness
+SELECT x,
+       CASE WHEN sind(x) IN (-1,-0.5,0,0.5,1) THEN sind(x) END AS sind,
+       CASE WHEN cosd(x) IN (-1,-0.5,0,0.5,1) THEN cosd(x) END AS cosd
+FROM generate_series(0, 360, 30) AS t(x);
+
+-- tangent and cotangent
+SELECT x,
+       tan(radians(x)) AS tan_radians, tand(x),
+       cot(radians(x)) AS cot_radians, cotd(x)
+FROM generate_series(-360, 360, 10) AS t(x)
+WHERE x = 0 OR x % 90 != 0; -- skip tests that are very platform-dependent
+
+-- test exactness
+SELECT x,
+       CASE WHEN tand(x) IN (-1,0,1) THEN tand(x) END AS tand,
+       CASE WHEN cotd(x) IN (-1,0,1) THEN cotd(x) END AS cotd
+FROM generate_series(0, 360, 45) AS t(x);
+
+-- arcsine and arccosine
+SELECT x,
+       degrees(asin(x)) AS degrees_asin, asind(x),
+       degrees(acos(x)) AS degrees_acos, acosd(x)
+FROM generate_series(-1.0, 1.0, 0.1) AS t(x);
+
+-- arctangent
+SELECT x, degrees(atan(x)) AS degrees_atan, atand(x)
+FROM (SELECT '-Infinity'::float8
+      UNION ALL
+      SELECT generate_series(-10, -2)
+      UNION ALL
+      SELECT generate_series(-1.0, 1.0, 0.1)
+      UNION ALL
+      SELECT generate_series(2, 10)
+      UNION ALL
+      SELECT 'Infinity'::float8
+      UNION ALL
+      SELECT 'NaN'::float8) AS t(x);
+
+-- 4 quadrant arctangent
+SELECT x, y, degrees(atan2(y, x)) AS degrees_atan2, atan2d(y, x)
+FROM (SELECT 10*cosd(a), 10*sind(a)
+      FROM generate_series(0, 360, 10) AS t(a)) AS t(x,y);
#43Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Michael Paquier (#42)
Re: Proposal: Trigonometric functions in degrees

On 11 November 2015 at 06:04, Michael Paquier <michael.paquier@gmail.com> wrote:

I also modified some of the CHECKFLOATVAL() checks which didn't look
right to me, unless there's some odd platform-specific behaviour that
I'm not aware of, functions like sin and asin should never return
infinity.

-       CHECKFLOATVAL(result, isinf(arg1), true);
+       CHECKFLOATVAL(result, false, true);
PG_RETURN_FLOAT8(result);

Hm. I would let them as-is, and update your patch to do the similar checks
in the new functions introduced. See f9ac414 from 2007 which is the result
of the following thread:
/messages/by-id/200612271844.kBRIiVb18465@momjian.us
It doesn't seem wise to be backward regarding those Inf/NaN checks.

The conclusion of that thread seemed to be that we ought to allow
silent underflows to 0, but any finite inputs that produce infinite
results ought to consider reporting an error.

That seems like a reasonable principle, but it's not what the code
actually does. For example, 1e-300::float8 * 1e-300::float8 generates
an error rather than silently underflowing to 0. In addition, some of
the checks appear to be backwards, for example the division functions
like float4div do the following:

CHECKFLOATVAL(result, isinf(arg1) || isinf(arg2), arg1 == 0);

whereas the result is 0 not infinity if arg2 is infinite (unless arg1
is also infinite, in which case it ought to be NaN).

In the case of functions like sin(), I can well believe that there are
some platforms for which sin(Infinity) is NaN, and others for which it
is an error, but are there really any for which the result is
infinite? If so, I'd argue that we should throw an error anyway --
sin(x) is supposed to be between -1 and 1, so I don't think allowing
an infinite result ever makes sense.

Anyway, this looks like a wider discussion than the scope of this
patch, so I'll revert those changes in this patch, and the decision
about what (if anything) should be done with those CHECKFLOATVAL
checks can be discussed separately.

The alternative expected outputs for test float8 need to be updated,
this is causing regression failures particularly on win32 where 3
digits are used for exponentials and where tan('NaN') actually results
in an ERROR. See for example the attached regressions.diffs.

It would be nice to split the results specific to NaN and Infinity into
separate queries. The test for arctangent is one where things should be
splitted.

Agreed. In fact, given the platform-dependent nature of those tests,
perhaps there's not much value in them at all.

+       if (isinf(arg1) || arg1 < -1 || arg1 > 1)
+               ereport(ERROR,
+
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+                                errmsg("input is out of range")));
This should check on -1.0 and 1.0?

Perhaps. I admit that I'm not terribly consistent when I write such
code and sometimes I just rely on implicit type promotion, and other
times I prefer to be explicit. Is there a project recommended style on
this? The current code seems to be somewhat inconsistent (e.g., dpow,
dlog1, dlog10 and setseed all have similar comparisons of doubles with
1, whereas float8_regr_syy, etc. compare against 1.0). I don't have
any particular preference, so I'll happily go with whatever other
people prefer, if there's a clear consensus.

+       if (arg1 > 180)
+       {
+               /* tand(360-x) = -tand(x) */
+        arg1 = 360 - arg1;
+               sign = -sign;
+       }
Picky detail: be careful of incorrect tab spaces.

Oops. Will fix.

=# select acos(-1.1);
acos
------
NaN
(1 row)

I get an error for that, so it's clearly platform-dependent.

=# select acosd(-1.1);
ERROR: 22003: input is out of range
LOCATION: dacosd, float.c:1752
Some results are inconsistent, it seems that we should return NaN in the
second case instead of an error.

I opted to have acosd() throw the same error that acos() does on my
platform, and I tend to think an error is more useful in this case.
Perhaps if consistency is important, we should modify the existing
functions to throw an error on all platforms, rather than being
platform-dependent.

I had as well a look at the monotony of those functions, using rough queries
like this one, and things are looking nice. The precise values are taken
into account and their surroundings are monotone.

Thanks for testing. I'll post an updated patch sometime soon.

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#44Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Dean Rasheed (#43)
1 attachment(s)
Re: Proposal: Trigonometric functions in degrees

On 11 November 2015 at 11:45, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

Thanks for testing. I'll post an updated patch sometime soon.

I finally got round to looking at this again, and here is an updated patch.

I've reverted the changes to the CHECKFLOATVAL() checks in the
existing functions, so that can be a separate discussion. As for the
checks in the new functions added by this patch, I've left them as
they were on the grounds that the checks are taking place after
argument reduction, so the arguments will not be infinite at that
point (and besides, I think the checks are correct as written anyway).

I've reduced the regression tests down to checks of the cases where
the results should be exact, so they should now be
platform-independent. Many of the original tests were useful during
development to ensure that sane-looking answers were being returned,
but they didn't really add anything as regression tests, other than
extra complication due to platform variations.

Regards,
Dean

Attachments:

trigd.patchtext/x-patch; charset=US-ASCII; name=trigd.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
new file mode 100644
index 60b9a09..3716210
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
***************
*** 993,1001 ****
     Finally, <xref linkend="functions-math-trig-table"> shows the
     available trigonometric functions.  All trigonometric functions
     take arguments and return values of type <type>double
!    precision</type>. Trigonometric functions arguments are expressed
!    in radians. Inverse functions return values are expressed in
!    radians.  See unit transformation functions
     <literal><function>radians()</function></literal> and
     <literal><function>degrees()</function></literal> above.
    </para>
--- 993,1001 ----
     Finally, <xref linkend="functions-math-trig-table"> shows the
     available trigonometric functions.  All trigonometric functions
     take arguments and return values of type <type>double
!    precision</type>.  Each of the trigonometric functions comes in
!    two varieties, one which works in radians and one which works in
!    degrees.  See unit transformation functions
     <literal><function>radians()</function></literal> and
     <literal><function>degrees()</function></literal> above.
    </para>
***************
*** 1003,1012 ****
     <table id="functions-math-trig-table">
      <title>Trigonometric Functions</title>
  
!     <tgroup cols="2">
       <thead>
        <row>
!        <entry>Function</entry>
         <entry>Description</entry>
        </row>
       </thead>
--- 1003,1013 ----
     <table id="functions-math-trig-table">
      <title>Trigonometric Functions</title>
  
!     <tgroup cols="3">
       <thead>
        <row>
!        <entry>Function (radians)</entry>
!        <entry>Function (degrees)</entry>
         <entry>Description</entry>
        </row>
       </thead>
***************
*** 1018,1023 ****
--- 1019,1029 ----
           <primary>acos</primary>
          </indexterm><literal><function>acos(<replaceable>x</replaceable>)</function></literal>
         </entry>
+        <entry>
+         <indexterm>
+          <primary>acosd</primary>
+         </indexterm><literal><function>acosd(<replaceable>x</replaceable>)</function></literal>
+        </entry>
         <entry>inverse cosine</entry>
        </row>
  
***************
*** 1028,1033 ****
--- 1034,1045 ----
          </indexterm>
          <literal><function>asin(<replaceable>x</replaceable>)</function></literal>
         </entry>
+        <entry>
+         <indexterm>
+          <primary>asind</primary>
+         </indexterm>
+         <literal><function>asind(<replaceable>x</replaceable>)</function></literal>
+        </entry>
         <entry>inverse sine</entry>
        </row>
  
***************
*** 1038,1043 ****
--- 1050,1061 ----
          </indexterm>
          <literal><function>atan(<replaceable>x</replaceable>)</function></literal>
         </entry>
+        <entry>
+         <indexterm>
+          <primary>atand</primary>
+         </indexterm>
+         <literal><function>atand(<replaceable>x</replaceable>)</function></literal>
+        </entry>
         <entry>inverse tangent</entry>
        </row>
  
***************
*** 1049,1054 ****
--- 1067,1079 ----
          <literal><function>atan2(<replaceable>y</replaceable>,
          <replaceable>x</replaceable>)</function></literal>
         </entry>
+        <entry>
+         <indexterm>
+          <primary>atan2d</primary>
+         </indexterm>
+         <literal><function>atan2d(<replaceable>y</replaceable>,
+         <replaceable>x</replaceable>)</function></literal>
+        </entry>
         <entry>inverse tangent of
          <literal><replaceable>y</replaceable>/<replaceable>x</replaceable></literal></entry>
        </row>
***************
*** 1060,1065 ****
--- 1085,1096 ----
          </indexterm>
          <literal><function>cos(<replaceable>x</replaceable>)</function></literal>
         </entry>
+        <entry>
+         <indexterm>
+          <primary>cosd</primary>
+         </indexterm>
+         <literal><function>cosd(<replaceable>x</replaceable>)</function></literal>
+        </entry>
         <entry>cosine</entry>
        </row>
  
***************
*** 1070,1075 ****
--- 1101,1112 ----
          </indexterm>
          <literal><function>cot(<replaceable>x</replaceable>)</function></literal>
         </entry>
+        <entry>
+         <indexterm>
+          <primary>cotd</primary>
+         </indexterm>
+         <literal><function>cotd(<replaceable>x</replaceable>)</function></literal>
+        </entry>
         <entry>cotangent</entry>
        </row>
  
***************
*** 1080,1085 ****
--- 1117,1128 ----
          </indexterm>
          <literal><function>sin(<replaceable>x</replaceable>)</function></literal>
         </entry>
+        <entry>
+         <indexterm>
+          <primary>sind</primary>
+         </indexterm>
+         <literal><function>sind(<replaceable>x</replaceable>)</function></literal>
+        </entry>
         <entry>sine</entry>
        </row>
  
***************
*** 1090,1095 ****
--- 1133,1144 ----
          </indexterm>
          <literal><function>tan(<replaceable>x</replaceable>)</function></literal>
         </entry>
+        <entry>
+         <indexterm>
+          <primary>tand</primary>
+         </indexterm>
+         <literal><function>tand(<replaceable>x</replaceable>)</function></literal>
+        </entry>
         <entry>tangent</entry>
        </row>
       </tbody>
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
new file mode 100644
index 4e927d8..4a6fdae
*** a/src/backend/utils/adt/float.c
--- b/src/backend/utils/adt/float.c
*************** dcot(PG_FUNCTION_ARGS)
*** 1642,1648 ****
  				 errmsg("input is out of range")));
  
  	result = 1.0 / result;
! 	CHECKFLOATVAL(result, true /* cotan(pi/2) == inf */ , true);
  	PG_RETURN_FLOAT8(result);
  }
  
--- 1642,1648 ----
  				 errmsg("input is out of range")));
  
  	result = 1.0 / result;
! 	CHECKFLOATVAL(result, true /* cot(0) == Inf */ , true);
  	PG_RETURN_FLOAT8(result);
  }
  
*************** dtan(PG_FUNCTION_ARGS)
*** 1688,1693 ****
--- 1688,2056 ----
  	PG_RETURN_FLOAT8(result);
  }
  
+ 
+ /*
+  *		asind_q1		- returns the inverse sine of x in degrees, where x is
+  *						  assumed to be in the range [0, 1] and the result is
+  *						  an angle in the first quadrant (0 to 90 degrees).
+  *
+  * In this quadrant there are 3 special case inputs (0, 0.5 and 1) for which
+  * this function will return exact values (0, 30 and 90 respectively).
+  */
+ static double
+ asind_q1(double x)
+ {
+ 	/*
+ 	 * Stitch together inverse sine and cosine functions for the ranges
+ 	 * [0, 0.5] and [0.5, 1].  Each expression below is guaranteed to return
+ 	 * exactly 30 for x=0.5, so the result is a continuous monotonic function
+ 	 * over the full range.
+ 	 */
+ 	if (x <= 0.5)
+ 		return (asin(x) / asin(0.5)) * 30.0;
+ 	else
+ 		return 90.0 - (acos(x) / acos(0.5)) * 60.0;
+ }
+ 
+ 
+ /*
+  *		acosd_q1		- returns the inverse cosine of x in degrees, where x
+  *						  is assumed to be in the range [0, 1] and the result
+  *						  is an angle in the first quadrant (0 to 90 degrees).
+  *
+  * In this quadrant there are 3 special case inputs (0, 0.5 and 1) for which
+  * this function will return exact values (0, 60 and 90 respectively).
+  */
+ static double
+ acosd_q1(double x)
+ {
+ 	/*
+ 	 * Stitch together inverse sine and cosine functions for the ranges
+ 	 * [0, 0.5] and [0.5, 1].  Each expression below is guaranteed to return
+ 	 * exactly 60 for x=0.5, so the result is a continuous monotonic function
+ 	 * over the full range.
+ 	 */
+ 	if (x <= 0.5)
+ 		return 90.0 - (asin(x) / asin(0.5)) * 30.0;
+ 	else
+ 		return (acos(x) / acos(0.5)) * 60.0;
+ }
+ 
+ 
+ /*
+  *		dacosd			- returns the arccos of arg1 (degrees)
+  */
+ Datum
+ dacosd(PG_FUNCTION_ARGS)
+ {
+ 	float8		arg1 = PG_GETARG_FLOAT8(0);
+ 	float8		result;
+ 
+ 	if (isinf(arg1) || arg1 < -1.0 || arg1 > 1.0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 				 errmsg("input is out of range")));
+ 
+ 	if (isnan(arg1))
+ 		result = arg1;
+ 	else if (arg1 >= 0.0)
+ 		result = acosd_q1(arg1);
+ 	else
+ 		result = 90.0 + asind_q1(-arg1);
+ 
+ 	CHECKFLOATVAL(result, false, true);
+ 	PG_RETURN_FLOAT8(result);
+ }
+ 
+ 
+ /*
+  *		dasind			- returns the arcsin of arg1 (degrees)
+  */
+ Datum
+ dasind(PG_FUNCTION_ARGS)
+ {
+ 	float8		arg1 = PG_GETARG_FLOAT8(0);
+ 	float8		result;
+ 
+ 	if (isinf(arg1) || arg1 < -1.0 || arg1 > 1.0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 				 errmsg("input is out of range")));
+ 
+ 	if (isnan(arg1))
+ 		result = arg1;
+ 	else if (arg1 >= 0.0)
+ 		result = asind_q1(arg1);
+ 	else
+ 		result = -asind_q1(-arg1);
+ 
+ 	CHECKFLOATVAL(result, false, true);
+ 	PG_RETURN_FLOAT8(result);
+ }
+ 
+ 
+ /*
+  *		datand			- returns the arctan of arg1 (degrees)
+  */
+ Datum
+ datand(PG_FUNCTION_ARGS)
+ {
+ 	float8		arg1 = PG_GETARG_FLOAT8(0);
+ 	float8		result;
+ 
+ 	errno = 0;
+ 	result = atan(arg1);
+ 	if (errno != 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 				 errmsg("input is out of range")));
+ 
+ 	result = (result / atan(1.0)) * 45.0;
+ 	CHECKFLOATVAL(result, false, true);
+ 	PG_RETURN_FLOAT8(result);
+ }
+ 
+ 
+ /*
+  *		atan2d			- returns the arctan2 of arg1 (degrees)
+  */
+ Datum
+ datan2d(PG_FUNCTION_ARGS)
+ {
+ 	float8		arg1 = PG_GETARG_FLOAT8(0);
+ 	float8		arg2 = PG_GETARG_FLOAT8(1);
+ 	float8		result;
+ 
+ 	errno = 0;
+ 	result = atan2(arg1, arg2);
+ 	if (errno != 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 				 errmsg("input is out of range")));
+ 
+ 	result = (result / atan(1.0)) * 45.0;
+ 	CHECKFLOATVAL(result, false, true);
+ 	PG_RETURN_FLOAT8(result);
+ }
+ 
+ 
+ /*
+  *		sind_0_to_30	- returns the sine of an angle that lies between 0 and
+  *						  30 degrees.  This will return exactly 0 when x is 0,
+  *						  and exactly 0.5 when x is 30 degrees.
+  */
+ static double
+ sind_0_to_30(double x)
+ {
+ 	return ( sin(x * (M_PI / 180.0)) / sin(30.0 * (M_PI / 180.0)) ) / 2.0;
+ }
+ 
+ 
+ /*
+  *		cosd_0_to_60	- returns the cosine of an angle that lies between 0
+  *						  and 60 degrees.  This will return exactly 1 when x
+  *						  is 0 and exactly 0.5 when x is 60 degrees.
+  */
+ static double
+ cosd_0_to_60(double x)
+ {
+ 	return ( 2.0 - (1.0 - cos(x * (M_PI / 180.0))) /
+ 					(1.0 - cos(60.0 * (M_PI / 180.0))) ) / 2.0;
+ }
+ 
+ 
+ /*
+  *		sind_q1			- returns the sine of an angle in the first quadrant
+  *						  (0 to 90 degrees).
+  */
+ static double
+ sind_q1(double x)
+ {
+ 	/*
+ 	 * Stitch together the sine and cosine functions for the ranges [0, 30]
+ 	 * and [30, 90].  These guarantee to return exact answers at their
+ 	 * endpoints, so the overall result is a continuous monotonic function
+ 	 * that gives exact results when x = 0, 30 and 90 degrees.
+ 	 */
+ 	return x <= 30.0 ? sind_0_to_30(x) : cosd_0_to_60(90.0 - x);
+ }
+ 
+ 
+ /*
+  *		cosd_q1			- returns the cosine of an angle in the first quadrant
+  *						  (0 to 90 degrees).
+  */
+ static double
+ cosd_q1(double x)
+ {
+ 	/*
+ 	 * Stitch together the sine and cosine functions for the ranges [0, 60]
+ 	 * and [60, 90].  These guarantee to return exact answers at their
+ 	 * endpoints, so the overall result is a continuous monotonic function
+ 	 * that gives exact results when x = 0, 60 and 90 degrees.
+ 	 */
+ 	return x <= 60.0 ? cosd_0_to_60(x) : sind_0_to_30(90.0 - x);
+ }
+ 
+ 
+ /*
+  *		dcosd			- returns the cosine of arg1 (degrees)
+  */
+ Datum
+ dcosd(PG_FUNCTION_ARGS)
+ {
+ 	float8		arg1 = PG_GETARG_FLOAT8(0);
+ 	int			sign = 1;
+ 	float8		result;
+ 
+ 	/* Reduce the range of the input to [0,90] degrees */
+ 	arg1 = fmod(arg1, 360.0);
+ 	if (isnan(arg1))
+ 		PG_RETURN_FLOAT8(arg1);
+ 
+ 	if (arg1 < 0.0)
+ 		/* cosd(-x) = cosd(x) */
+ 		arg1 = -arg1;
+ 
+ 	if (arg1 > 180.0)
+ 		/* cosd(360-x) = cosd(x) */
+ 		arg1 = 360.0 - arg1;
+ 
+ 	if (arg1 > 90.0)
+ 	{
+ 		/* cosd(180-x) = -cosd(x) */
+ 		arg1 = 180.0 - arg1;
+ 		sign = -sign;
+ 	}
+ 
+ 	result = sign * cosd_q1(arg1);
+ 	CHECKFLOATVAL(result, false, true);
+ 	PG_RETURN_FLOAT8(result);
+ }
+ 
+ 
+ /*
+  *		dcotd			- returns the cotangent of arg1 (degrees)
+  */
+ Datum
+ dcotd(PG_FUNCTION_ARGS)
+ {
+ 	float8		arg1 = PG_GETARG_FLOAT8(0);
+ 	int			sign = 1;
+ 	float8		result;
+ 
+ 	/* Reduce the range of the input to [0,90] degrees */
+ 	arg1 = fmod(arg1, 360.0);
+ 	if (isnan(arg1))
+ 		PG_RETURN_FLOAT8(arg1);
+ 
+ 	if (arg1 < 0.0)
+ 	{
+ 		/* cotd(-x) = -cotd(x) */
+ 		arg1 = -arg1;
+ 		sign = -sign;
+ 	}
+ 
+ 	if (arg1 > 180.0)
+ 	{
+ 		/* cotd(360-x) = -cotd(x) */
+ 		arg1 = 360.0 - arg1;
+ 		sign = -sign;
+ 	}
+ 
+ 	if (arg1 > 90.0)
+ 	{
+ 		/* cotd(180-x) = -cotd(x) */
+ 		arg1 = 180.0 - arg1;
+ 		sign = -sign;
+ 	}
+ 
+ 	result = sign * cosd_q1(arg1) / sind_q1(arg1);
+ 	CHECKFLOATVAL(result, true /* cotd(0) == Inf */ , true);
+ 	PG_RETURN_FLOAT8(result);
+ }
+ 
+ 
+ /*
+  *		dsind			- returns the sine of arg1 (degrees)
+  */
+ Datum
+ dsind(PG_FUNCTION_ARGS)
+ {
+ 	float8		arg1 = PG_GETARG_FLOAT8(0);
+ 	int			sign = 1;
+ 	float8		result;
+ 
+ 	/* Reduce the range of the input to [0,90] degrees */
+ 	arg1 = fmod(arg1, 360.0);
+ 	if (isnan(arg1))
+ 		PG_RETURN_FLOAT8(arg1);
+ 
+ 	if (arg1 < 0.0)
+ 	{
+ 		/* sind(-x) = -sind(x) */
+ 		arg1 = -arg1;
+ 		sign = -sign;
+ 	}
+ 
+ 	if (arg1 > 180.0)
+ 	{
+ 		/* sind(360-x) = -sind(x) */
+ 		arg1 = 360.0 - arg1;
+ 		sign = -sign;
+ 	}
+ 
+ 	if (arg1 > 90.0)
+ 		/* sind(180-x) = sind(x) */
+ 		arg1 = 180.0 - arg1;
+ 
+ 	result = sign * sind_q1(arg1);
+ 	CHECKFLOATVAL(result, false, true);
+ 	PG_RETURN_FLOAT8(result);
+ }
+ 
+ 
+ /*
+  *		dtand			- returns the tangent of arg1 (degrees)
+  */
+ Datum
+ dtand(PG_FUNCTION_ARGS)
+ {
+ 	float8		arg1 = PG_GETARG_FLOAT8(0);
+ 	int			sign = 1;
+ 	float8		result;
+ 
+ 	/* Reduce the range of the input to [0,90] degrees */
+ 	arg1 = fmod(arg1, 360.0);
+ 	if (isnan(arg1))
+ 		PG_RETURN_FLOAT8(arg1);
+ 
+ 	if (arg1 < 0.0)
+ 	{
+ 		/* tand(-x) = -tand(x) */
+ 		arg1 = -arg1;
+ 		sign = -sign;
+ 	}
+ 
+ 	if (arg1 > 180.0)
+ 	{
+ 		/* tand(360-x) = -tand(x) */
+ 		arg1 = 360.0 - arg1;
+ 		sign = -sign;
+ 	}
+ 
+ 	if (arg1 > 90.0)
+ 	{
+ 		/* tand(180-x) = -tand(x) */
+ 		arg1 = 180.0 - arg1;
+ 		sign = -sign;
+ 	}
+ 
+ 	result = sign * sind_q1(arg1) / cosd_q1(arg1);
+ 	CHECKFLOATVAL(result, true /* tand(90) == Inf */ , true);
+ 	PG_RETURN_FLOAT8(result);
+ }
+ 
  
  /*
   *		degrees		- returns degrees converted from radians
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
new file mode 100644
index d8640db..f4d468e
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DATA(insert OID = 1606 (  tan				PGNSP P
*** 1894,1899 ****
--- 1894,1917 ----
  DESCR("tangent");
  DATA(insert OID = 1607 (  cot				PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701 "701" _null_ _null_ _null_ _null_ _null_ dcot _null_ _null_ _null_ ));
  DESCR("cotangent");
+ 
+ DATA(insert OID = 3317 (  asind				PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701 "701" _null_ _null_ _null_ _null_ _null_ dasind _null_ _null_ _null_ ));
+ DESCR("arcsine, degrees");
+ DATA(insert OID = 3318 (  acosd				PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701 "701" _null_ _null_ _null_ _null_ _null_ dacosd _null_ _null_ _null_ ));
+ DESCR("arccosine, degrees");
+ DATA(insert OID = 3319 (  atand				PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701 "701" _null_ _null_ _null_ _null_ _null_ datand _null_ _null_ _null_ ));
+ DESCR("arctangent, degrees");
+ DATA(insert OID = 3320 (  atan2d			PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 701 "701 701" _null_ _null_ _null_ _null_ _null_ datan2d _null_ _null_ _null_ ));
+ DESCR("arctangent, two arguments, degrees");
+ DATA(insert OID = 3321 (  sind				PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701 "701" _null_ _null_ _null_ _null_ _null_ dsind _null_ _null_ _null_ ));
+ DESCR("sine, degrees");
+ DATA(insert OID = 3322 (  cosd				PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701 "701" _null_ _null_ _null_ _null_ _null_ dcosd _null_ _null_ _null_ ));
+ DESCR("cosine, degrees");
+ DATA(insert OID = 3323 (  tand				PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701 "701" _null_ _null_ _null_ _null_ _null_ dtand _null_ _null_ _null_ ));
+ DESCR("tangent, degrees");
+ DATA(insert OID = 3324 (  cotd				PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701 "701" _null_ _null_ _null_ _null_ _null_ dcotd _null_ _null_ _null_ ));
+ DESCR("cotangent, degrees");
+ 
  DATA(insert OID = 1608 (  degrees			PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701 "701" _null_ _null_ _null_ _null_ _null_ degrees _null_ _null_ _null_ ));
  DESCR("radians to degrees");
  DATA(insert OID = 1609 (  radians			PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701 "701" _null_ _null_ _null_ _null_ _null_ radians _null_ _null_ _null_ ));
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
new file mode 100644
index e610bf3..cbca7f3
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum dcos(PG_FUNCTION_ARGS);
*** 407,412 ****
--- 407,420 ----
  extern Datum dcot(PG_FUNCTION_ARGS);
  extern Datum dsin(PG_FUNCTION_ARGS);
  extern Datum dtan(PG_FUNCTION_ARGS);
+ extern Datum dacosd(PG_FUNCTION_ARGS);
+ extern Datum dasind(PG_FUNCTION_ARGS);
+ extern Datum datand(PG_FUNCTION_ARGS);
+ extern Datum datan2d(PG_FUNCTION_ARGS);
+ extern Datum dcosd(PG_FUNCTION_ARGS);
+ extern Datum dcotd(PG_FUNCTION_ARGS);
+ extern Datum dsind(PG_FUNCTION_ARGS);
+ extern Datum dtand(PG_FUNCTION_ARGS);
  extern Datum degrees(PG_FUNCTION_ARGS);
  extern Datum dpi(PG_FUNCTION_ARGS);
  extern Datum radians(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/float8-exp-three-digits-win32.out b/src/test/regress/expected/float8-exp-three-digits-win32.out
new file mode 100644
index 2dd648d..6891ee0
*** a/src/test/regress/expected/float8-exp-three-digits-win32.out
--- b/src/test/regress/expected/float8-exp-three-digits-win32.out
*************** SELECT '' AS five, * FROM FLOAT8_TBL;
*** 444,446 ****
--- 444,523 ----
        | -1.2345678901234e-200
  (5 rows)
  
+ -- test exact cases for trigonometric functions in degrees
+ SELECT x,
+        CASE WHEN sind(x) IN (-1,-0.5,0,0.5,1) THEN sind(x) END AS sind,
+        CASE WHEN cosd(x) IN (-1,-0.5,0,0.5,1) THEN cosd(x) END AS cosd,
+        CASE WHEN tand(x) IN ('-Infinity'::float8,-1,0,
+                              1,'Infinity'::float8) THEN tand(x) END AS tand,
+        CASE WHEN cotd(x) IN ('-Infinity'::float8,-1,0,
+                              1,'Infinity'::float8) THEN cotd(x) END AS cotd
+ FROM generate_series(0, 360, 15) AS t(x);
+   x  | sind | cosd |   tand    |   cotd    
+ -----+------+------+-----------+-----------
+    0 |    0 |    1 |         0 |  Infinity
+   15 |      |      |           |          
+   30 |  0.5 |      |           |          
+   45 |      |      |         1 |         1
+   60 |      |  0.5 |           |          
+   75 |      |      |           |          
+   90 |    1 |    0 |  Infinity |         0
+  105 |      |      |           |          
+  120 |      | -0.5 |           |          
+  135 |      |      |        -1 |        -1
+  150 |  0.5 |      |           |          
+  165 |      |      |           |          
+  180 |    0 |   -1 |        -0 | -Infinity
+  195 |      |      |           |          
+  210 | -0.5 |      |           |          
+  225 |      |      |         1 |         1
+  240 |      | -0.5 |           |          
+  255 |      |      |           |          
+  270 |   -1 |    0 | -Infinity |        -0
+  285 |      |      |           |          
+  300 |      |  0.5 |           |          
+  315 |      |      |        -1 |        -1
+  330 | -0.5 |      |           |          
+  345 |      |      |           |          
+  360 |    0 |    1 |         0 |  Infinity
+ (25 rows)
+ 
+ SELECT x,
+        CASE WHEN asind(x) IN (-90,-30,0,30,90) THEN asind(x) END AS asind,
+        CASE WHEN acosd(x) IN (0,60,90,120,180) THEN acosd(x) END AS acosd,
+        CASE WHEN atand(x) IN (-45,0,45) THEN atand(x) END AS atand
+ FROM (VALUES (-1), (-0.5), (0), (0.5), (1)) AS t(x);
+   x   | asind | acosd | atand 
+ ------+-------+-------+-------
+    -1 |   -90 |   180 |   -45
+  -0.5 |   -30 |   120 |      
+     0 |     0 |    90 |     0
+   0.5 |    30 |    60 |      
+     1 |    90 |     0 |    45
+ (5 rows)
+ 
+ SELECT atand('-Infinity'::float8) = -90;
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ SELECT atand('Infinity'::float8) = 90;
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ SELECT x, y,
+        CASE WHEN atan2d(y, x) IN (-90,0,90,180) THEN atan2d(y, x) END AS atan2d
+ FROM (SELECT 10*cosd(a), 10*sind(a)
+       FROM generate_series(0, 360, 90) AS t(a)) AS t(x,y);
+   x  |  y  | atan2d 
+ -----+-----+--------
+   10 |   0 |      0
+    0 |  10 |     90
+  -10 |   0 |    180
+    0 | -10 |    -90
+   10 |   0 |      0
+ (5 rows)
+ 
diff --git a/src/test/regress/expected/float8-small-is-zero.out b/src/test/regress/expected/float8-small-is-zero.out
new file mode 100644
index 5da7433..e158e70
*** a/src/test/regress/expected/float8-small-is-zero.out
--- b/src/test/regress/expected/float8-small-is-zero.out
*************** SELECT '' AS five, * FROM FLOAT8_TBL;
*** 442,444 ****
--- 442,521 ----
        | -1.2345678901234e-200
  (5 rows)
  
+ -- test exact cases for trigonometric functions in degrees
+ SELECT x,
+        CASE WHEN sind(x) IN (-1,-0.5,0,0.5,1) THEN sind(x) END AS sind,
+        CASE WHEN cosd(x) IN (-1,-0.5,0,0.5,1) THEN cosd(x) END AS cosd,
+        CASE WHEN tand(x) IN ('-Infinity'::float8,-1,0,
+                              1,'Infinity'::float8) THEN tand(x) END AS tand,
+        CASE WHEN cotd(x) IN ('-Infinity'::float8,-1,0,
+                              1,'Infinity'::float8) THEN cotd(x) END AS cotd
+ FROM generate_series(0, 360, 15) AS t(x);
+   x  | sind | cosd |   tand    |   cotd    
+ -----+------+------+-----------+-----------
+    0 |    0 |    1 |         0 |  Infinity
+   15 |      |      |           |          
+   30 |  0.5 |      |           |          
+   45 |      |      |         1 |         1
+   60 |      |  0.5 |           |          
+   75 |      |      |           |          
+   90 |    1 |    0 |  Infinity |         0
+  105 |      |      |           |          
+  120 |      | -0.5 |           |          
+  135 |      |      |        -1 |        -1
+  150 |  0.5 |      |           |          
+  165 |      |      |           |          
+  180 |    0 |   -1 |        -0 | -Infinity
+  195 |      |      |           |          
+  210 | -0.5 |      |           |          
+  225 |      |      |         1 |         1
+  240 |      | -0.5 |           |          
+  255 |      |      |           |          
+  270 |   -1 |    0 | -Infinity |        -0
+  285 |      |      |           |          
+  300 |      |  0.5 |           |          
+  315 |      |      |        -1 |        -1
+  330 | -0.5 |      |           |          
+  345 |      |      |           |          
+  360 |    0 |    1 |         0 |  Infinity
+ (25 rows)
+ 
+ SELECT x,
+        CASE WHEN asind(x) IN (-90,-30,0,30,90) THEN asind(x) END AS asind,
+        CASE WHEN acosd(x) IN (0,60,90,120,180) THEN acosd(x) END AS acosd,
+        CASE WHEN atand(x) IN (-45,0,45) THEN atand(x) END AS atand
+ FROM (VALUES (-1), (-0.5), (0), (0.5), (1)) AS t(x);
+   x   | asind | acosd | atand 
+ ------+-------+-------+-------
+    -1 |   -90 |   180 |   -45
+  -0.5 |   -30 |   120 |      
+     0 |     0 |    90 |     0
+   0.5 |    30 |    60 |      
+     1 |    90 |     0 |    45
+ (5 rows)
+ 
+ SELECT atand('-Infinity'::float8) = -90;
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ SELECT atand('Infinity'::float8) = 90;
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ SELECT x, y,
+        CASE WHEN atan2d(y, x) IN (-90,0,90,180) THEN atan2d(y, x) END AS atan2d
+ FROM (SELECT 10*cosd(a), 10*sind(a)
+       FROM generate_series(0, 360, 90) AS t(a)) AS t(x,y);
+   x  |  y  | atan2d 
+ -----+-----+--------
+   10 |   0 |      0
+    0 |  10 |     90
+  -10 |   0 |    180
+    0 | -10 |    -90
+   10 |   0 |      0
+ (5 rows)
+ 
diff --git a/src/test/regress/expected/float8-small-is-zero_1.out b/src/test/regress/expected/float8-small-is-zero_1.out
new file mode 100644
index 530842e..42e50a0
*** a/src/test/regress/expected/float8-small-is-zero_1.out
--- b/src/test/regress/expected/float8-small-is-zero_1.out
*************** SELECT '' AS five, * FROM FLOAT8_TBL;
*** 442,444 ****
--- 442,521 ----
        | -1.2345678901234e-200
  (5 rows)
  
+ -- test exact cases for trigonometric functions in degrees
+ SELECT x,
+        CASE WHEN sind(x) IN (-1,-0.5,0,0.5,1) THEN sind(x) END AS sind,
+        CASE WHEN cosd(x) IN (-1,-0.5,0,0.5,1) THEN cosd(x) END AS cosd,
+        CASE WHEN tand(x) IN ('-Infinity'::float8,-1,0,
+                              1,'Infinity'::float8) THEN tand(x) END AS tand,
+        CASE WHEN cotd(x) IN ('-Infinity'::float8,-1,0,
+                              1,'Infinity'::float8) THEN cotd(x) END AS cotd
+ FROM generate_series(0, 360, 15) AS t(x);
+   x  | sind | cosd |   tand    |   cotd    
+ -----+------+------+-----------+-----------
+    0 |    0 |    1 |         0 |  Infinity
+   15 |      |      |           |          
+   30 |  0.5 |      |           |          
+   45 |      |      |         1 |         1
+   60 |      |  0.5 |           |          
+   75 |      |      |           |          
+   90 |    1 |    0 |  Infinity |         0
+  105 |      |      |           |          
+  120 |      | -0.5 |           |          
+  135 |      |      |        -1 |        -1
+  150 |  0.5 |      |           |          
+  165 |      |      |           |          
+  180 |    0 |   -1 |        -0 | -Infinity
+  195 |      |      |           |          
+  210 | -0.5 |      |           |          
+  225 |      |      |         1 |         1
+  240 |      | -0.5 |           |          
+  255 |      |      |           |          
+  270 |   -1 |    0 | -Infinity |        -0
+  285 |      |      |           |          
+  300 |      |  0.5 |           |          
+  315 |      |      |        -1 |        -1
+  330 | -0.5 |      |           |          
+  345 |      |      |           |          
+  360 |    0 |    1 |         0 |  Infinity
+ (25 rows)
+ 
+ SELECT x,
+        CASE WHEN asind(x) IN (-90,-30,0,30,90) THEN asind(x) END AS asind,
+        CASE WHEN acosd(x) IN (0,60,90,120,180) THEN acosd(x) END AS acosd,
+        CASE WHEN atand(x) IN (-45,0,45) THEN atand(x) END AS atand
+ FROM (VALUES (-1), (-0.5), (0), (0.5), (1)) AS t(x);
+   x   | asind | acosd | atand 
+ ------+-------+-------+-------
+    -1 |   -90 |   180 |   -45
+  -0.5 |   -30 |   120 |      
+     0 |     0 |    90 |     0
+   0.5 |    30 |    60 |      
+     1 |    90 |     0 |    45
+ (5 rows)
+ 
+ SELECT atand('-Infinity'::float8) = -90;
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ SELECT atand('Infinity'::float8) = 90;
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ SELECT x, y,
+        CASE WHEN atan2d(y, x) IN (-90,0,90,180) THEN atan2d(y, x) END AS atan2d
+ FROM (SELECT 10*cosd(a), 10*sind(a)
+       FROM generate_series(0, 360, 90) AS t(a)) AS t(x,y);
+   x  |  y  | atan2d 
+ -----+-----+--------
+   10 |   0 |      0
+    0 |  10 |     90
+  -10 |   0 |    180
+    0 | -10 |    -90
+   10 |   0 |      0
+ (5 rows)
+ 
diff --git a/src/test/regress/expected/float8.out b/src/test/regress/expected/float8.out
new file mode 100644
index 6221538..b77b34f
*** a/src/test/regress/expected/float8.out
--- b/src/test/regress/expected/float8.out
*************** SELECT '' AS five, * FROM FLOAT8_TBL;
*** 444,446 ****
--- 444,523 ----
        | -1.2345678901234e-200
  (5 rows)
  
+ -- test exact cases for trigonometric functions in degrees
+ SELECT x,
+        CASE WHEN sind(x) IN (-1,-0.5,0,0.5,1) THEN sind(x) END AS sind,
+        CASE WHEN cosd(x) IN (-1,-0.5,0,0.5,1) THEN cosd(x) END AS cosd,
+        CASE WHEN tand(x) IN ('-Infinity'::float8,-1,0,
+                              1,'Infinity'::float8) THEN tand(x) END AS tand,
+        CASE WHEN cotd(x) IN ('-Infinity'::float8,-1,0,
+                              1,'Infinity'::float8) THEN cotd(x) END AS cotd
+ FROM generate_series(0, 360, 15) AS t(x);
+   x  | sind | cosd |   tand    |   cotd    
+ -----+------+------+-----------+-----------
+    0 |    0 |    1 |         0 |  Infinity
+   15 |      |      |           |          
+   30 |  0.5 |      |           |          
+   45 |      |      |         1 |         1
+   60 |      |  0.5 |           |          
+   75 |      |      |           |          
+   90 |    1 |    0 |  Infinity |         0
+  105 |      |      |           |          
+  120 |      | -0.5 |           |          
+  135 |      |      |        -1 |        -1
+  150 |  0.5 |      |           |          
+  165 |      |      |           |          
+  180 |    0 |   -1 |        -0 | -Infinity
+  195 |      |      |           |          
+  210 | -0.5 |      |           |          
+  225 |      |      |         1 |         1
+  240 |      | -0.5 |           |          
+  255 |      |      |           |          
+  270 |   -1 |    0 | -Infinity |        -0
+  285 |      |      |           |          
+  300 |      |  0.5 |           |          
+  315 |      |      |        -1 |        -1
+  330 | -0.5 |      |           |          
+  345 |      |      |           |          
+  360 |    0 |    1 |         0 |  Infinity
+ (25 rows)
+ 
+ SELECT x,
+        CASE WHEN asind(x) IN (-90,-30,0,30,90) THEN asind(x) END AS asind,
+        CASE WHEN acosd(x) IN (0,60,90,120,180) THEN acosd(x) END AS acosd,
+        CASE WHEN atand(x) IN (-45,0,45) THEN atand(x) END AS atand
+ FROM (VALUES (-1), (-0.5), (0), (0.5), (1)) AS t(x);
+   x   | asind | acosd | atand 
+ ------+-------+-------+-------
+    -1 |   -90 |   180 |   -45
+  -0.5 |   -30 |   120 |      
+     0 |     0 |    90 |     0
+   0.5 |    30 |    60 |      
+     1 |    90 |     0 |    45
+ (5 rows)
+ 
+ SELECT atand('-Infinity'::float8) = -90;
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ SELECT atand('Infinity'::float8) = 90;
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ SELECT x, y,
+        CASE WHEN atan2d(y, x) IN (-90,0,90,180) THEN atan2d(y, x) END AS atan2d
+ FROM (SELECT 10*cosd(a), 10*sind(a)
+       FROM generate_series(0, 360, 90) AS t(a)) AS t(x,y);
+   x  |  y  | atan2d 
+ -----+-----+--------
+   10 |   0 |      0
+    0 |  10 |     90
+  -10 |   0 |    180
+    0 | -10 |    -90
+   10 |   0 |      0
+ (5 rows)
+ 
diff --git a/src/test/regress/sql/float8.sql b/src/test/regress/sql/float8.sql
new file mode 100644
index 92a574a..45a484b
*** a/src/test/regress/sql/float8.sql
--- b/src/test/regress/sql/float8.sql
*************** INSERT INTO FLOAT8_TBL(f1) VALUES ('-1.2
*** 167,169 ****
--- 167,193 ----
  INSERT INTO FLOAT8_TBL(f1) VALUES ('-1.2345678901234e-200');
  
  SELECT '' AS five, * FROM FLOAT8_TBL;
+ 
+ -- test exact cases for trigonometric functions in degrees
+ SELECT x,
+        CASE WHEN sind(x) IN (-1,-0.5,0,0.5,1) THEN sind(x) END AS sind,
+        CASE WHEN cosd(x) IN (-1,-0.5,0,0.5,1) THEN cosd(x) END AS cosd,
+        CASE WHEN tand(x) IN ('-Infinity'::float8,-1,0,
+                              1,'Infinity'::float8) THEN tand(x) END AS tand,
+        CASE WHEN cotd(x) IN ('-Infinity'::float8,-1,0,
+                              1,'Infinity'::float8) THEN cotd(x) END AS cotd
+ FROM generate_series(0, 360, 15) AS t(x);
+ 
+ SELECT x,
+        CASE WHEN asind(x) IN (-90,-30,0,30,90) THEN asind(x) END AS asind,
+        CASE WHEN acosd(x) IN (0,60,90,120,180) THEN acosd(x) END AS acosd,
+        CASE WHEN atand(x) IN (-45,0,45) THEN atand(x) END AS atand
+ FROM (VALUES (-1), (-0.5), (0), (0.5), (1)) AS t(x);
+ 
+ SELECT atand('-Infinity'::float8) = -90;
+ SELECT atand('Infinity'::float8) = 90;
+ 
+ SELECT x, y,
+        CASE WHEN atan2d(y, x) IN (-90,0,90,180) THEN atan2d(y, x) END AS atan2d
+ FROM (SELECT 10*cosd(a), 10*sind(a)
+       FROM generate_series(0, 360, 90) AS t(a)) AS t(x,y);
#45Michael Paquier
michael.paquier@gmail.com
In reply to: Dean Rasheed (#44)
Re: Proposal: Trigonometric functions in degrees

On Fri, Nov 27, 2015 at 6:33 PM, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

On 11 November 2015 at 11:45, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

Thanks for testing. I'll post an updated patch sometime soon.

I finally got round to looking at this again, and here is an updated patch.

Cool, thanks!

I've reverted the changes to the CHECKFLOATVAL() checks in the
existing functions, so that can be a separate discussion. As for the
checks in the new functions added by this patch, I've left them as
they were on the grounds that the checks are taking place after
argument reduction, so the arguments will not be infinite at that
point (and besides, I think the checks are correct as written anyway).

On this one, I agree less for the new node but I am fine to let the
committer take the final shot. Making things similar to the existing
functions seems the best approach to me though.

I've reduced the regression tests down to checks of the cases where
the results should be exact, so they should now be
platform-independent. Many of the original tests were useful during
development to ensure that sane-looking answers were being returned,
but they didn't really add anything as regression tests, other than
extra complication due to platform variations.

That's definitely a better thing to do. I got surprised as well for
example by how things behave differently on OSX, Linux and Windows
when testing your patch :)

+        * Stitch together inverse sine and cosine functions for the ranges
+        * [0, 0.5] and [0.5, 1].  Each expression below is guaranteed to return
+        * exactly 30 for x=0.5, so the result is a continuous
monotonic function
+        * over the full range.
+        * Stitch together inverse sine and cosine functions for the ranges
+        * [0, 0.5] and [0.5, 1].  Each expression below is guaranteed to return
+        * exactly 60 for x=0.5, so the result is a continuous
monotonic function
+        * over the full range.

Those two should mention [0,0.5] and (0.5,1]. 0.5 is only part of the
first portion. There are a couple of places where that's not exact as
well, but no real big deal.

Now on OSX the following things are inconsistent:
1) acos:
=# select acosd(1.1);
ERROR: 22003: input is out of range
LOCATION: dacosd, float.c:1752
Time: 0.623 ms
=# select acos(1.1);
acos
------
NaN
(1 row)
=# select asind('Infinity'::float8);
ERROR: 22003: input is out of range
LOCATION: dasind, float.c:1778
2) asin:
=# select asind(1.1);
ERROR: 22003: input is out of range
LOCATION: dasind, float.c:1778
=# select asin(1.1);
asin
------
NaN
(1 row)
Instinctively, it seems to me that we had better return Nan for the
new asind and acosd when being out of range for OSX, Linux will
complain about an out-of-range error so the code is right in this
case.

3) those ones as well are interesting:
=# select tand(180);
tand
------
-0
(1 row)
=# select cotd(-90);
cotd
------
-0

Regards,
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#46Michael Paquier
michael.paquier@gmail.com
In reply to: Michael Paquier (#45)
Re: Proposal: Trigonometric functions in degrees

On Mon, Nov 30, 2015 at 10:36 PM, Michael Paquier wrote:

Instinctively, it seems to me that we had better return Nan for the
new asind and acosd when being out of range for OSX, Linux will
complain about an out-of-range error so the code is right in this
case.

This is still mentioned upthread btw. And it does not seem to be a
good idea to change this platform dependent behavior by throwing an
error on the old functions, neither does it seem user-friendly to have
inconsistent results for the XXX function and its XXXd equivalent.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#47Michael Paquier
michael.paquier@gmail.com
In reply to: Michael Paquier (#46)
Re: Proposal: Trigonometric functions in degrees

On Mon, Nov 30, 2015 at 10:39 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:

On Mon, Nov 30, 2015 at 10:36 PM, Michael Paquier wrote:

Instinctively, it seems to me that we had better return Nan for the
new asind and acosd when being out of range for OSX, Linux will
complain about an out-of-range error so the code is right in this
case.

This is still mentioned upthread btw. And it does not seem to be a
good idea to change this platform dependent behavior by throwing an
error on the old functions, neither does it seem user-friendly to have
inconsistent results for the XXX function and its XXXd equivalent.

At this stage, it seems wiser to me to mark this patch as "returned
with feedback". Other opinions of course are welcome.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#48Tom Lane
tgl@sss.pgh.pa.us
In reply to: Michael Paquier (#46)
Re: Proposal: Trigonometric functions in degrees

Michael Paquier <michael.paquier@gmail.com> writes:

On Mon, Nov 30, 2015 at 10:36 PM, Michael Paquier wrote:

Instinctively, it seems to me that we had better return Nan for the
new asind and acosd when being out of range for OSX, Linux will
complain about an out-of-range error so the code is right in this
case.

This is still mentioned upthread btw. And it does not seem to be a
good idea to change this platform dependent behavior by throwing an
error on the old functions, neither does it seem user-friendly to have
inconsistent results for the XXX function and its XXXd equivalent.

FWIW, I think that probably the best course of action is to go ahead
and install POSIX-compliant error checking in the existing functions
too. POSIX:2008 is quite clear about this:

: An application wishing to check for error situations should set errno to
: zero and call feclearexcept(FE_ALL_EXCEPT) before calling these
: functions. On return, if errno is non-zero or fetestexcept(FE_INVALID |
: FE_DIVBYZERO | FE_OVERFLOW | FE_UNDERFLOW) is non-zero, an error has
: occurred.

Although I'm not too sure about Windows, the inconsistent results
we're getting on OS X are certainly from failure to adhere to the spec.

I concur with Peter's opinion that this is material for a separate
patch, but it seems like it's one that had better go in first.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#49Michael Paquier
michael.paquier@gmail.com
In reply to: Tom Lane (#48)
Re: Proposal: Trigonometric functions in degrees

On Mon, Nov 30, 2015 at 11:11 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Michael Paquier <michael.paquier@gmail.com> writes:

On Mon, Nov 30, 2015 at 10:36 PM, Michael Paquier wrote:

Instinctively, it seems to me that we had better return Nan for the
new asind and acosd when being out of range for OSX, Linux will
complain about an out-of-range error so the code is right in this
case.

This is still mentioned upthread btw. And it does not seem to be a
good idea to change this platform dependent behavior by throwing an
error on the old functions, neither does it seem user-friendly to have
inconsistent results for the XXX function and its XXXd equivalent.

FWIW, I think that probably the best course of action is to go ahead
and install POSIX-compliant error checking in the existing functions
too. POSIX:2008 is quite clear about this:

: An application wishing to check for error situations should set errno to
: zero and call feclearexcept(FE_ALL_EXCEPT) before calling these
: functions. On return, if errno is non-zero or fetestexcept(FE_INVALID |
: FE_DIVBYZERO | FE_OVERFLOW | FE_UNDERFLOW) is non-zero, an error has
: occurred.

OK, I have to admit I didn't know this part.

Although I'm not too sure about Windows, the inconsistent results
we're getting on OS X are certainly from failure to adhere to the spec.

Windows complains about out-of-range errors contrary to OSX on for
example asin or acos. So for once Linux and Windows agree with each
other.

I concur with Peter's opinion that this is material for a separate
patch, but it seems like it's one that had better go in first.

(I think you mean Dean here and not Peter).
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#50Michael Paquier
michael.paquier@gmail.com
In reply to: Michael Paquier (#49)
Re: Proposal: Trigonometric functions in degrees

On Mon, Nov 30, 2015 at 11:24 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:

On Mon, Nov 30, 2015 at 11:11 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Michael Paquier <michael.paquier@gmail.com> writes:

On Mon, Nov 30, 2015 at 10:36 PM, Michael Paquier wrote:

Instinctively, it seems to me that we had better return Nan for the
new asind and acosd when being out of range for OSX, Linux will
complain about an out-of-range error so the code is right in this
case.

This is still mentioned upthread btw. And it does not seem to be a
good idea to change this platform dependent behavior by throwing an
error on the old functions, neither does it seem user-friendly to have
inconsistent results for the XXX function and its XXXd equivalent.

FWIW, I think that probably the best course of action is to go ahead
and install POSIX-compliant error checking in the existing functions
too. POSIX:2008 is quite clear about this:

: An application wishing to check for error situations should set errno to
: zero and call feclearexcept(FE_ALL_EXCEPT) before calling these
: functions. On return, if errno is non-zero or fetestexcept(FE_INVALID |
: FE_DIVBYZERO | FE_OVERFLOW | FE_UNDERFLOW) is non-zero, an error has
: occurred.

OK, I have to admit I didn't know this part.

Although I'm not too sure about Windows, the inconsistent results
we're getting on OS X are certainly from failure to adhere to the spec.

Windows complains about out-of-range errors contrary to OSX on for
example asin or acos. So for once Linux and Windows agree with each
other.

I concur with Peter's opinion that this is material for a separate
patch, but it seems like it's one that had better go in first.

(I think you mean Dean here and not Peter).

Dean, are you planning to continue working on this patch? If yes, are
you fine to move it to next CF? It seems that the current consensus is
to split this effort into two patches:
1) Harden error checks for existing functions, particularly the
inconsistencies for asin and acos.
2) Have the new functions in degrees.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#51Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Michael Paquier (#50)
Re: Proposal: Trigonometric functions in degrees

On 1 December 2015 at 12:59, Michael Paquier <michael.paquier@gmail.com> wrote:

Dean, are you planning to continue working on this patch? If yes, are
you fine to move it to next CF? It seems that the current consensus is
to split this effort into two patches:

Yes, I still plan to work on it. I might not get much time over the
next few days, so moving it to the next CF is probably reasonable.

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#52Michael Paquier
michael.paquier@gmail.com
In reply to: Dean Rasheed (#51)
Re: Proposal: Trigonometric functions in degrees

On Wed, Dec 2, 2015 at 3:30 AM, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

On 1 December 2015 at 12:59, Michael Paquier <michael.paquier@gmail.com> wrote:

Dean, are you planning to continue working on this patch? If yes, are
you fine to move it to next CF? It seems that the current consensus is
to split this effort into two patches:

Yes, I still plan to work on it. I might not get much time over the
next few days, so moving it to the next CF is probably reasonable.

Deal.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#53Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Tom Lane (#48)
2 attachment(s)
Re: Proposal: Trigonometric functions in degrees

On 30 November 2015 at 14:11, Tom Lane <tgl@sss.pgh.pa.us> wrote:

FWIW, I think that probably the best course of action is to go ahead
and install POSIX-compliant error checking in the existing functions
too. POSIX:2008 is quite clear about this:

: An application wishing to check for error situations should set errno to
: zero and call feclearexcept(FE_ALL_EXCEPT) before calling these
: functions. On return, if errno is non-zero or fetestexcept(FE_INVALID |
: FE_DIVBYZERO | FE_OVERFLOW | FE_UNDERFLOW) is non-zero, an error has
: occurred.

Looking at this again, I think it makes sense to make the Inf/NaN
handling of these functions platform-independent. POSIX.1-2008 is
pretty explicit about how they ought to behave, which is different
from the current behaviour on either Linux or Windows:

sin(Inf):
POSIX: domain error
Linux: NaN
Windows: ERROR: input is out of range

asin(Inf):
POSIX: domain error
Linux: ERROR: input is out of range
Windows: ERROR: input is out of range

sin(NaN):
POSIX: NaN
Linux: NaN
Windows: ERROR: input is out of range

asin(NaN):
POSIX: NaN
Linux: NaN
Windows: ERROR: input is out of range

I tried using feclearexcept + fetestexcept as recommended by
POSIX.1-2008, and that does indeed make the above examples compliant
on my linux box. Unfortunately it introduces new errors -- asin starts
generating FE_UNDERFLOW errors for numbers that are really not that
small, such as asin(1e-9), although the function still returns the
correct answer. A bigger problem though is that these are C99
functions and so won't necessarily be available on all supported
platforms.

So I think that a simpler answer is to just to add explicit tests for
these inputs and avoid relying on errno, at least for the inverse
functions, which have pretty clear constraints on their allowed
inputs. For the forward functions, I'm not sure if there are some
platforms on which large but finite inputs might generate errors, so I
think it's safest to keep the existing errno checks as well just in
case.

Attached are patches for this and the new functions in degrees, now
with POSIX compatible Inf/NaN handling.

Regards,
Dean

Attachments:

trig-fn-range-checks.patchtext/x-patch; charset=US-ASCII; name=trig-fn-range-checks.patchDownload
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
new file mode 100644
index 8f34209..4ce0129
*** a/src/backend/utils/adt/float.c
--- b/src/backend/utils/adt/float.c
*************** dacos(PG_FUNCTION_ARGS)
*** 1524,1541 ****
  	float8		arg1 = PG_GETARG_FLOAT8(0);
  	float8		result;
  
  	/*
! 	 * We use errno here because the trigonometric functions are cyclic and
! 	 * hard to check for underflow.
  	 */
! 	errno = 0;
! 	result = acos(arg1);
! 	if (errno != 0)
  		ereport(ERROR,
  				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
  				 errmsg("input is out of range")));
  
! 	CHECKFLOATVAL(result, isinf(arg1), true);
  	PG_RETURN_FLOAT8(result);
  }
  
--- 1524,1546 ----
  	float8		arg1 = PG_GETARG_FLOAT8(0);
  	float8		result;
  
+ 	/* Per the POSIX spec, return NaN if the input is NaN */
+ 	if (isnan(arg1))
+ 		PG_RETURN_FLOAT8(get_float8_nan());
+ 
  	/*
! 	 * The principal branch of the inverse cosine function maps values in the
! 	 * range [-1, 1] to values in the range [0, Pi], so we should reject any
! 	 * inputs outside that range and the result will always be finite.
  	 */
! 	if (arg1 < -1.0 || arg1 > 1.0)
  		ereport(ERROR,
  				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
  				 errmsg("input is out of range")));
  
! 	result = acos(arg1);
! 
! 	CHECKFLOATVAL(result, false, true);
  	PG_RETURN_FLOAT8(result);
  }
  
*************** dasin(PG_FUNCTION_ARGS)
*** 1549,1562 ****
  	float8		arg1 = PG_GETARG_FLOAT8(0);
  	float8		result;
  
! 	errno = 0;
! 	result = asin(arg1);
! 	if (errno != 0)
  		ereport(ERROR,
  				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
  				 errmsg("input is out of range")));
  
! 	CHECKFLOATVAL(result, isinf(arg1), true);
  	PG_RETURN_FLOAT8(result);
  }
  
--- 1554,1576 ----
  	float8		arg1 = PG_GETARG_FLOAT8(0);
  	float8		result;
  
! 	/* Per the POSIX spec, return NaN if the input is NaN */
! 	if (isnan(arg1))
! 		PG_RETURN_FLOAT8(get_float8_nan());
! 
! 	/*
! 	 * The principal branch of the inverse sine function maps values in the
! 	 * range [-1, 1] to values in the range [-Pi/2, Pi/2], so we should reject
! 	 * any inputs outside that range and the result will always be finite.
! 	 */
! 	if (arg1 < -1.0 || arg1 > 1.0)
  		ereport(ERROR,
  				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
  				 errmsg("input is out of range")));
  
! 	result = asin(arg1);
! 
! 	CHECKFLOATVAL(result, false, true);
  	PG_RETURN_FLOAT8(result);
  }
  
*************** datan(PG_FUNCTION_ARGS)
*** 1570,1583 ****
  	float8		arg1 = PG_GETARG_FLOAT8(0);
  	float8		result;
  
! 	errno = 0;
  	result = atan(arg1);
- 	if (errno != 0)
- 		ereport(ERROR,
- 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
- 				 errmsg("input is out of range")));
  
! 	CHECKFLOATVAL(result, isinf(arg1), true);
  	PG_RETURN_FLOAT8(result);
  }
  
--- 1584,1601 ----
  	float8		arg1 = PG_GETARG_FLOAT8(0);
  	float8		result;
  
! 	/* Per the POSIX spec, return NaN if the input is NaN */
! 	if (isnan(arg1))
! 		PG_RETURN_FLOAT8(get_float8_nan());
! 
! 	/*
! 	 * The principal branch of the inverse tangent function maps all inputs to
! 	 * values in the range [-Pi/2, Pi/2], so the result should always be
! 	 * finite, even if the input is infinite.
! 	 */
  	result = atan(arg1);
  
! 	CHECKFLOATVAL(result, false, true);
  	PG_RETURN_FLOAT8(result);
  }
  
*************** datan2(PG_FUNCTION_ARGS)
*** 1592,1605 ****
  	float8		arg2 = PG_GETARG_FLOAT8(1);
  	float8		result;
  
! 	errno = 0;
  	result = atan2(arg1, arg2);
- 	if (errno != 0)
- 		ereport(ERROR,
- 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
- 				 errmsg("input is out of range")));
  
! 	CHECKFLOATVAL(result, isinf(arg1) || isinf(arg2), true);
  	PG_RETURN_FLOAT8(result);
  }
  
--- 1610,1626 ----
  	float8		arg2 = PG_GETARG_FLOAT8(1);
  	float8		result;
  
! 	/* Per the POSIX spec, return NaN if either input is NaN */
! 	if (isnan(arg1) || isnan(arg2))
! 		PG_RETURN_FLOAT8(get_float8_nan());
! 
! 	/*
! 	 * atan2 maps all inputs to values in the range [-Pi, Pi], so the result
! 	 * should always be finite, even if the inputs are infinite.
! 	 */
  	result = atan2(arg1, arg2);
  
! 	CHECKFLOATVAL(result, false, true);
  	PG_RETURN_FLOAT8(result);
  }
  
*************** dcos(PG_FUNCTION_ARGS)
*** 1613,1626 ****
  	float8		arg1 = PG_GETARG_FLOAT8(0);
  	float8		result;
  
  	errno = 0;
  	result = cos(arg1);
! 	if (errno != 0)
  		ereport(ERROR,
  				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
  				 errmsg("input is out of range")));
  
! 	CHECKFLOATVAL(result, isinf(arg1), true);
  	PG_RETURN_FLOAT8(result);
  }
  
--- 1634,1660 ----
  	float8		arg1 = PG_GETARG_FLOAT8(0);
  	float8		result;
  
+ 	/* Per the POSIX spec, return NaN if the input is NaN */
+ 	if (isnan(arg1))
+ 		PG_RETURN_FLOAT8(get_float8_nan());
+ 
+ 	/*
+ 	 * The trigonometric functions are periodic and so should work for all
+ 	 * finite inputs, but it's possible that some platforms may have a more
+ 	 * limited range, so we use errno to detect other out-of-range inputs.
+ 	 *
+ 	 * For infinite inputs, POSIX specifies that the trigonometric functions
+ 	 * should return errno == EDOM, but not all platforms conform to that, so
+ 	 * we explicitly test for infinite inputs if errno is not set.
+ 	 */
  	errno = 0;
  	result = cos(arg1);
! 	if (errno != 0 || isinf(arg1))
  		ereport(ERROR,
  				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
  				 errmsg("input is out of range")));
  
! 	CHECKFLOATVAL(result, false /* result is always finite */ , true);
  	PG_RETURN_FLOAT8(result);
  }
  
*************** dcot(PG_FUNCTION_ARGS)
*** 1634,1648 ****
  	float8		arg1 = PG_GETARG_FLOAT8(0);
  	float8		result;
  
  	errno = 0;
  	result = tan(arg1);
! 	if (errno != 0)
  		ereport(ERROR,
  				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
  				 errmsg("input is out of range")));
  
  	result = 1.0 / result;
! 	CHECKFLOATVAL(result, true /* cotan(pi/2) == inf */ , true);
  	PG_RETURN_FLOAT8(result);
  }
  
--- 1668,1687 ----
  	float8		arg1 = PG_GETARG_FLOAT8(0);
  	float8		result;
  
+ 	/* Per the POSIX spec, return NaN if the input is NaN */
+ 	if (isnan(arg1))
+ 		PG_RETURN_FLOAT8(get_float8_nan());
+ 
+ 	/* Be sure to throw an error if the input is infinite --- see dcos */
  	errno = 0;
  	result = tan(arg1);
! 	if (errno != 0 || isinf(arg1))
  		ereport(ERROR,
  				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
  				 errmsg("input is out of range")));
  
  	result = 1.0 / result;
! 	CHECKFLOATVAL(result, true /* cot(0) == Inf */ , true);
  	PG_RETURN_FLOAT8(result);
  }
  
*************** dsin(PG_FUNCTION_ARGS)
*** 1656,1669 ****
  	float8		arg1 = PG_GETARG_FLOAT8(0);
  	float8		result;
  
  	errno = 0;
  	result = sin(arg1);
! 	if (errno != 0)
  		ereport(ERROR,
  				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
  				 errmsg("input is out of range")));
  
! 	CHECKFLOATVAL(result, isinf(arg1), true);
  	PG_RETURN_FLOAT8(result);
  }
  
--- 1695,1713 ----
  	float8		arg1 = PG_GETARG_FLOAT8(0);
  	float8		result;
  
+ 	/* Per the POSIX spec, return NaN if the input is NaN */
+ 	if (isnan(arg1))
+ 		PG_RETURN_FLOAT8(get_float8_nan());
+ 
+ 	/* Be sure to throw an error if the input is infinite --- see dcos */
  	errno = 0;
  	result = sin(arg1);
! 	if (errno != 0 || isinf(arg1))
  		ereport(ERROR,
  				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
  				 errmsg("input is out of range")));
  
! 	CHECKFLOATVAL(result, false /* result is always finite */ , true);
  	PG_RETURN_FLOAT8(result);
  }
  
*************** dtan(PG_FUNCTION_ARGS)
*** 1677,1685 ****
  	float8		arg1 = PG_GETARG_FLOAT8(0);
  	float8		result;
  
  	errno = 0;
  	result = tan(arg1);
! 	if (errno != 0)
  		ereport(ERROR,
  				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
  				 errmsg("input is out of range")));
--- 1721,1734 ----
  	float8		arg1 = PG_GETARG_FLOAT8(0);
  	float8		result;
  
+ 	/* Per the POSIX spec, return NaN if the input is NaN */
+ 	if (isnan(arg1))
+ 		PG_RETURN_FLOAT8(get_float8_nan());
+ 
+ 	/* Be sure to throw an error if the input is infinite --- see dcos */
  	errno = 0;
  	result = tan(arg1);
! 	if (errno != 0 || isinf(arg1))
  		ereport(ERROR,
  				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
  				 errmsg("input is out of range")));
trigd.patchtext/x-patch; charset=US-ASCII; name=trigd.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
new file mode 100644
index 0af01d9..d9a218c
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
***************
*** 1006,1014 ****
     Finally, <xref linkend="functions-math-trig-table"> shows the
     available trigonometric functions.  All trigonometric functions
     take arguments and return values of type <type>double
!    precision</type>. Trigonometric functions arguments are expressed
!    in radians. Inverse functions return values are expressed in
!    radians.  See unit transformation functions
     <literal><function>radians()</function></literal> and
     <literal><function>degrees()</function></literal> above.
    </para>
--- 1006,1014 ----
     Finally, <xref linkend="functions-math-trig-table"> shows the
     available trigonometric functions.  All trigonometric functions
     take arguments and return values of type <type>double
!    precision</type>.  Each of the trigonometric functions comes in
!    two varieties, one which works in radians and one which works in
!    degrees.  See unit transformation functions
     <literal><function>radians()</function></literal> and
     <literal><function>degrees()</function></literal> above.
    </para>
***************
*** 1016,1025 ****
     <table id="functions-math-trig-table">
      <title>Trigonometric Functions</title>
  
!     <tgroup cols="2">
       <thead>
        <row>
!        <entry>Function</entry>
         <entry>Description</entry>
        </row>
       </thead>
--- 1016,1026 ----
     <table id="functions-math-trig-table">
      <title>Trigonometric Functions</title>
  
!     <tgroup cols="3">
       <thead>
        <row>
!        <entry>Function (radians)</entry>
!        <entry>Function (degrees)</entry>
         <entry>Description</entry>
        </row>
       </thead>
***************
*** 1031,1036 ****
--- 1032,1042 ----
           <primary>acos</primary>
          </indexterm><literal><function>acos(<replaceable>x</replaceable>)</function></literal>
         </entry>
+        <entry>
+         <indexterm>
+          <primary>acosd</primary>
+         </indexterm><literal><function>acosd(<replaceable>x</replaceable>)</function></literal>
+        </entry>
         <entry>inverse cosine</entry>
        </row>
  
***************
*** 1041,1046 ****
--- 1047,1058 ----
          </indexterm>
          <literal><function>asin(<replaceable>x</replaceable>)</function></literal>
         </entry>
+        <entry>
+         <indexterm>
+          <primary>asind</primary>
+         </indexterm>
+         <literal><function>asind(<replaceable>x</replaceable>)</function></literal>
+        </entry>
         <entry>inverse sine</entry>
        </row>
  
***************
*** 1051,1056 ****
--- 1063,1074 ----
          </indexterm>
          <literal><function>atan(<replaceable>x</replaceable>)</function></literal>
         </entry>
+        <entry>
+         <indexterm>
+          <primary>atand</primary>
+         </indexterm>
+         <literal><function>atand(<replaceable>x</replaceable>)</function></literal>
+        </entry>
         <entry>inverse tangent</entry>
        </row>
  
***************
*** 1062,1067 ****
--- 1080,1092 ----
          <literal><function>atan2(<replaceable>y</replaceable>,
          <replaceable>x</replaceable>)</function></literal>
         </entry>
+        <entry>
+         <indexterm>
+          <primary>atan2d</primary>
+         </indexterm>
+         <literal><function>atan2d(<replaceable>y</replaceable>,
+         <replaceable>x</replaceable>)</function></literal>
+        </entry>
         <entry>inverse tangent of
          <literal><replaceable>y</replaceable>/<replaceable>x</replaceable></literal></entry>
        </row>
***************
*** 1073,1078 ****
--- 1098,1109 ----
          </indexterm>
          <literal><function>cos(<replaceable>x</replaceable>)</function></literal>
         </entry>
+        <entry>
+         <indexterm>
+          <primary>cosd</primary>
+         </indexterm>
+         <literal><function>cosd(<replaceable>x</replaceable>)</function></literal>
+        </entry>
         <entry>cosine</entry>
        </row>
  
***************
*** 1083,1088 ****
--- 1114,1125 ----
          </indexterm>
          <literal><function>cot(<replaceable>x</replaceable>)</function></literal>
         </entry>
+        <entry>
+         <indexterm>
+          <primary>cotd</primary>
+         </indexterm>
+         <literal><function>cotd(<replaceable>x</replaceable>)</function></literal>
+        </entry>
         <entry>cotangent</entry>
        </row>
  
***************
*** 1093,1098 ****
--- 1130,1141 ----
          </indexterm>
          <literal><function>sin(<replaceable>x</replaceable>)</function></literal>
         </entry>
+        <entry>
+         <indexterm>
+          <primary>sind</primary>
+         </indexterm>
+         <literal><function>sind(<replaceable>x</replaceable>)</function></literal>
+        </entry>
         <entry>sine</entry>
        </row>
  
***************
*** 1103,1108 ****
--- 1146,1157 ----
          </indexterm>
          <literal><function>tan(<replaceable>x</replaceable>)</function></literal>
         </entry>
+        <entry>
+         <indexterm>
+          <primary>tand</primary>
+         </indexterm>
+         <literal><function>tand(<replaceable>x</replaceable>)</function></literal>
+        </entry>
         <entry>tangent</entry>
        </row>
       </tbody>
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
new file mode 100644
index 8f34209..9d48406
*** a/src/backend/utils/adt/float.c
--- b/src/backend/utils/adt/float.c
*************** dtan(PG_FUNCTION_ARGS)
*** 1690,1695 ****
--- 1690,2130 ----
  
  
  /*
+  *		asind_q1		- returns the inverse sine of x in degrees, for x in
+  *						  the range [0, 1].  The result is an angle in the
+  *						  first quadrant --- [0, 90] degrees.
+  *
+  *						  For the 3 special case inputs (0, 0.5 and 1), this
+  *						  function will return exact values (0, 30 and 90
+  *						  degrees respectively).
+  */
+ static double
+ asind_q1(double x)
+ {
+ 	/*
+ 	 * Stitch together inverse sine and cosine functions for the ranges
+ 	 * [0, 0.5] and (0.5, 1].  Each expression below is guaranteed to return
+ 	 * exactly 30 for x=0.5, so the result is a continuous monotonic function
+ 	 * over the full range.
+ 	 */
+ 	if (x <= 0.5)
+ 		return (asin(x) / asin(0.5)) * 30.0;
+ 	else
+ 		return 90.0 - (acos(x) / acos(0.5)) * 60.0;
+ }
+ 
+ 
+ /*
+  *		acosd_q1		- returns the inverse cosine of x in degrees, for x in
+  *						  the range [0, 1].  The result is an angle in the
+  *						  first quadrant --- [0, 90] degrees.
+  *
+  *						  For the 3 special case inputs (0, 0.5 and 1), this
+  *						  function will return exact values (0, 60 and 90
+  *						  degrees respectively).
+  */
+ static double
+ acosd_q1(double x)
+ {
+ 	/*
+ 	 * Stitch together inverse sine and cosine functions for the ranges
+ 	 * [0, 0.5] and (0.5, 1].  Each expression below is guaranteed to return
+ 	 * exactly 60 for x=0.5, so the result is a continuous monotonic function
+ 	 * over the full range.
+ 	 */
+ 	if (x <= 0.5)
+ 		return 90.0 - (asin(x) / asin(0.5)) * 30.0;
+ 	else
+ 		return (acos(x) / acos(0.5)) * 60.0;
+ }
+ 
+ 
+ /*
+  *		dacosd			- returns the arccos of arg1 (degrees)
+  */
+ Datum
+ dacosd(PG_FUNCTION_ARGS)
+ {
+ 	float8		arg1 = PG_GETARG_FLOAT8(0);
+ 	float8		result;
+ 
+ 	/* Per the POSIX spec, return NaN if the input is NaN */
+ 	if (isnan(arg1))
+ 		PG_RETURN_FLOAT8(get_float8_nan());
+ 
+ 	/*
+ 	 * The principal branch of the inverse cosine function maps values in the
+ 	 * range [-1, 1] to values in the range [0, 180], so we should reject any
+ 	 * inputs outside that range and the result will always be finite.
+ 	 */
+ 	if (arg1 < -1.0 || arg1 > 1.0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 				 errmsg("input is out of range")));
+ 
+ 	if (arg1 >= 0.0)
+ 		result = acosd_q1(arg1);
+ 	else
+ 		result = 90.0 + asind_q1(-arg1);
+ 
+ 	CHECKFLOATVAL(result, false, true);
+ 	PG_RETURN_FLOAT8(result);
+ }
+ 
+ 
+ /*
+  *		dasind			- returns the arcsin of arg1 (degrees)
+  */
+ Datum
+ dasind(PG_FUNCTION_ARGS)
+ {
+ 	float8		arg1 = PG_GETARG_FLOAT8(0);
+ 	float8		result;
+ 
+ 	/* Per the POSIX spec, return NaN if the input is NaN */
+ 	if (isnan(arg1))
+ 		PG_RETURN_FLOAT8(get_float8_nan());
+ 
+ 	/*
+ 	 * The principal branch of the inverse sine function maps values in the
+ 	 * range [-1, 1] to values in the range [-90, 90], so we should reject
+ 	 * any inputs outside that range and the result will always be finite.
+ 	 */
+ 	if (arg1 < -1.0 || arg1 > 1.0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 				 errmsg("input is out of range")));
+ 
+ 	if (arg1 >= 0.0)
+ 		result = asind_q1(arg1);
+ 	else
+ 		result = -asind_q1(-arg1);
+ 
+ 	CHECKFLOATVAL(result, false, true);
+ 	PG_RETURN_FLOAT8(result);
+ }
+ 
+ 
+ /*
+  *		datand			- returns the arctan of arg1 (degrees)
+  */
+ Datum
+ datand(PG_FUNCTION_ARGS)
+ {
+ 	float8		arg1 = PG_GETARG_FLOAT8(0);
+ 	float8		result;
+ 
+ 	/* Per the POSIX spec, return NaN if the input is NaN */
+ 	if (isnan(arg1))
+ 		PG_RETURN_FLOAT8(get_float8_nan());
+ 
+ 	/*
+ 	 * The principal branch of the inverse tangent function maps all inputs to
+ 	 * values in the range [-90, 90], so the result should always be finite,
+ 	 * even if the input is infinite.  Additionally, we take care to ensure
+ 	 * than when arg1 is 1, the result is exactly 45.
+ 	 */
+ 	result = (atan(arg1) / atan(1.0)) * 45.0;
+ 
+ 	CHECKFLOATVAL(result, false, true);
+ 	PG_RETURN_FLOAT8(result);
+ }
+ 
+ 
+ /*
+  *		atan2d			- returns the arctan2 of arg1 (degrees)
+  */
+ Datum
+ datan2d(PG_FUNCTION_ARGS)
+ {
+ 	float8		arg1 = PG_GETARG_FLOAT8(0);
+ 	float8		arg2 = PG_GETARG_FLOAT8(1);
+ 	float8		result;
+ 
+ 	/* Per the POSIX spec, return NaN if either input is NaN */
+ 	if (isnan(arg1) || isnan(arg2))
+ 		PG_RETURN_FLOAT8(get_float8_nan());
+ 
+ 	/*
+ 	 * atan2d maps all inputs to values in the range [-180, 180], so the
+ 	 * result should always be finite, even if the inputs are infinite.
+ 	 */
+ 	result = (atan2(arg1, arg2) / atan(1.0)) * 45.0;
+ 
+ 	CHECKFLOATVAL(result, false, true);
+ 	PG_RETURN_FLOAT8(result);
+ }
+ 
+ 
+ /*
+  *		sind_0_to_30	- returns the sine of an angle that lies between 0 and
+  *						  30 degrees.  This will return exactly 0 when x is 0,
+  *						  and exactly 0.5 when x is 30 degrees.
+  */
+ static double
+ sind_0_to_30(double x)
+ {
+ 	return ( sin(x * (M_PI / 180.0)) / sin(30.0 * (M_PI / 180.0)) ) / 2.0;
+ }
+ 
+ 
+ /*
+  *		cosd_0_to_60	- returns the cosine of an angle that lies between 0
+  *						  and 60 degrees.  This will return exactly 1 when x
+  *						  is 0 and exactly 0.5 when x is 60 degrees.
+  */
+ static double
+ cosd_0_to_60(double x)
+ {
+ 	return ( 2.0 - (1.0 - cos(x * (M_PI / 180.0))) /
+ 					(1.0 - cos(60.0 * (M_PI / 180.0))) ) / 2.0;
+ }
+ 
+ 
+ /*
+  *		sind_q1			- returns the sine of an angle in the first quadrant
+  *						  (0 to 90 degrees).
+  */
+ static double
+ sind_q1(double x)
+ {
+ 	/*
+ 	 * Stitch together the sine and cosine functions for the ranges [0, 30]
+ 	 * and (30, 90].  These guarantee to return exact answers at their
+ 	 * endpoints, so the overall result is a continuous monotonic function
+ 	 * that gives exact results when x = 0, 30 and 90 degrees.
+ 	 */
+ 	if (x <= 30.0)
+ 		return sind_0_to_30(x);
+ 	else
+ 		return cosd_0_to_60(90.0 - x);
+ }
+ 
+ 
+ /*
+  *		cosd_q1			- returns the cosine of an angle in the first quadrant
+  *						  (0 to 90 degrees).
+  */
+ static double
+ cosd_q1(double x)
+ {
+ 	/*
+ 	 * Stitch together the sine and cosine functions for the ranges [0, 60]
+ 	 * and (60, 90].  These guarantee to return exact answers at their
+ 	 * endpoints, so the overall result is a continuous monotonic function
+ 	 * that gives exact results when x = 0, 60 and 90 degrees.
+ 	 */
+ 	if (x <= 60.0)
+ 		return cosd_0_to_60(x);
+ 	else
+ 		return sind_0_to_30(90.0 - x);
+ }
+ 
+ 
+ /*
+  *		dcosd			- returns the cosine of arg1 (degrees)
+  */
+ Datum
+ dcosd(PG_FUNCTION_ARGS)
+ {
+ 	float8		arg1 = PG_GETARG_FLOAT8(0);
+ 	int			sign = 1;
+ 	float8		result;
+ 
+ 	/*
+ 	 * Per the POSIX spec, return NaN if the input is NaN and throw an error
+ 	 * if the input is infinite.
+ 	 */
+ 	if (isnan(arg1))
+ 		PG_RETURN_FLOAT8(get_float8_nan());
+ 
+ 	if (isinf(arg1))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 				 errmsg("input is out of range")));
+ 
+ 	/* Reduce the range of the input to [0,90] degrees */
+ 	arg1 = fmod(arg1, 360.0);
+ 
+ 	if (arg1 < 0.0)
+ 		/* cosd(-x) = cosd(x) */
+ 		arg1 = -arg1;
+ 
+ 	if (arg1 > 180.0)
+ 		/* cosd(360-x) = cosd(x) */
+ 		arg1 = 360.0 - arg1;
+ 
+ 	if (arg1 > 90.0)
+ 	{
+ 		/* cosd(180-x) = -cosd(x) */
+ 		arg1 = 180.0 - arg1;
+ 		sign = -sign;
+ 	}
+ 
+ 	result = sign * cosd_q1(arg1);
+ 
+ 	CHECKFLOATVAL(result, false /* result is always finite */ , true);
+ 	PG_RETURN_FLOAT8(result);
+ }
+ 
+ 
+ /*
+  *		dcotd			- returns the cotangent of arg1 (degrees)
+  */
+ Datum
+ dcotd(PG_FUNCTION_ARGS)
+ {
+ 	float8		arg1 = PG_GETARG_FLOAT8(0);
+ 	int			sign = 1;
+ 	float8		result;
+ 
+ 	/*
+ 	 * Per the POSIX spec, return NaN if the input is NaN and throw an error
+ 	 * if the input is infinite.
+ 	 */
+ 	if (isnan(arg1))
+ 		PG_RETURN_FLOAT8(get_float8_nan());
+ 
+ 	if (isinf(arg1))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 				 errmsg("input is out of range")));
+ 
+ 	/* Reduce the range of the input to [0,90] degrees */
+ 	arg1 = fmod(arg1, 360.0);
+ 
+ 	if (arg1 < 0.0)
+ 	{
+ 		/* cotd(-x) = -cotd(x) */
+ 		arg1 = -arg1;
+ 		sign = -sign;
+ 	}
+ 
+ 	if (arg1 > 180.0)
+ 	{
+ 		/* cotd(360-x) = -cotd(x) */
+ 		arg1 = 360.0 - arg1;
+ 		sign = -sign;
+ 	}
+ 
+ 	if (arg1 > 90.0)
+ 	{
+ 		/* cotd(180-x) = -cotd(x) */
+ 		arg1 = 180.0 - arg1;
+ 		sign = -sign;
+ 	}
+ 
+ 	result = sign * cosd_q1(arg1) / sind_q1(arg1);
+ 
+ 	CHECKFLOATVAL(result, true /* cotd(0) == Inf */ , true);
+ 	PG_RETURN_FLOAT8(result);
+ }
+ 
+ 
+ /*
+  *		dsind			- returns the sine of arg1 (degrees)
+  */
+ Datum
+ dsind(PG_FUNCTION_ARGS)
+ {
+ 	float8		arg1 = PG_GETARG_FLOAT8(0);
+ 	int			sign = 1;
+ 	float8		result;
+ 
+ 	/*
+ 	 * Per the POSIX spec, return NaN if the input is NaN and throw an error
+ 	 * if the input is infinite.
+ 	 */
+ 	if (isnan(arg1))
+ 		PG_RETURN_FLOAT8(get_float8_nan());
+ 
+ 	if (isinf(arg1))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 				 errmsg("input is out of range")));
+ 
+ 	/* Reduce the range of the input to [0,90] degrees */
+ 	arg1 = fmod(arg1, 360.0);
+ 
+ 	if (arg1 < 0.0)
+ 	{
+ 		/* sind(-x) = -sind(x) */
+ 		arg1 = -arg1;
+ 		sign = -sign;
+ 	}
+ 
+ 	if (arg1 > 180.0)
+ 	{
+ 		/* sind(360-x) = -sind(x) */
+ 		arg1 = 360.0 - arg1;
+ 		sign = -sign;
+ 	}
+ 
+ 	if (arg1 > 90.0)
+ 		/* sind(180-x) = sind(x) */
+ 		arg1 = 180.0 - arg1;
+ 
+ 	result = sign * sind_q1(arg1);
+ 
+ 	CHECKFLOATVAL(result, false /* result is always finite */ , true);
+ 	PG_RETURN_FLOAT8(result);
+ }
+ 
+ 
+ /*
+  *		dtand			- returns the tangent of arg1 (degrees)
+  */
+ Datum
+ dtand(PG_FUNCTION_ARGS)
+ {
+ 	float8		arg1 = PG_GETARG_FLOAT8(0);
+ 	int			sign = 1;
+ 	float8		result;
+ 
+ 	/*
+ 	 * Per the POSIX spec, return NaN if the input is NaN and throw an error
+ 	 * if the input is infinite.
+ 	 */
+ 	if (isnan(arg1))
+ 		PG_RETURN_FLOAT8(get_float8_nan());
+ 
+ 	if (isinf(arg1))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 				 errmsg("input is out of range")));
+ 
+ 	/* Reduce the range of the input to [0,90] degrees */
+ 	arg1 = fmod(arg1, 360.0);
+ 
+ 	if (arg1 < 0.0)
+ 	{
+ 		/* tand(-x) = -tand(x) */
+ 		arg1 = -arg1;
+ 		sign = -sign;
+ 	}
+ 
+ 	if (arg1 > 180.0)
+ 	{
+ 		/* tand(360-x) = -tand(x) */
+ 		arg1 = 360.0 - arg1;
+ 		sign = -sign;
+ 	}
+ 
+ 	if (arg1 > 90.0)
+ 	{
+ 		/* tand(180-x) = -tand(x) */
+ 		arg1 = 180.0 - arg1;
+ 		sign = -sign;
+ 	}
+ 
+ 	result = sign * sind_q1(arg1) / cosd_q1(arg1);
+ 
+ 	CHECKFLOATVAL(result, true /* tand(90) == Inf */ , true);
+ 	PG_RETURN_FLOAT8(result);
+ }
+ 
+ 
+ /*
   *		degrees		- returns degrees converted from radians
   */
  Datum
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
new file mode 100644
index f58672e..3c67b91
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DATA(insert OID = 1606 (  tan				PGNSP P
*** 1894,1899 ****
--- 1894,1917 ----
  DESCR("tangent");
  DATA(insert OID = 1607 (  cot				PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701 "701" _null_ _null_ _null_ _null_ _null_ dcot _null_ _null_ _null_ ));
  DESCR("cotangent");
+ 
+ DATA(insert OID = 3318 (  asind				PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701 "701" _null_ _null_ _null_ _null_ _null_ dasind _null_ _null_ _null_ ));
+ DESCR("arcsine, degrees");
+ DATA(insert OID = 3319 (  acosd				PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701 "701" _null_ _null_ _null_ _null_ _null_ dacosd _null_ _null_ _null_ ));
+ DESCR("arccosine, degrees");
+ DATA(insert OID = 3320 (  atand				PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701 "701" _null_ _null_ _null_ _null_ _null_ datand _null_ _null_ _null_ ));
+ DESCR("arctangent, degrees");
+ DATA(insert OID = 3321 (  atan2d			PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 701 "701 701" _null_ _null_ _null_ _null_ _null_ datan2d _null_ _null_ _null_ ));
+ DESCR("arctangent, two arguments, degrees");
+ DATA(insert OID = 3322 (  sind				PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701 "701" _null_ _null_ _null_ _null_ _null_ dsind _null_ _null_ _null_ ));
+ DESCR("sine, degrees");
+ DATA(insert OID = 3323 (  cosd				PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701 "701" _null_ _null_ _null_ _null_ _null_ dcosd _null_ _null_ _null_ ));
+ DESCR("cosine, degrees");
+ DATA(insert OID = 3324 (  tand				PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701 "701" _null_ _null_ _null_ _null_ _null_ dtand _null_ _null_ _null_ ));
+ DESCR("tangent, degrees");
+ DATA(insert OID = 3325 (  cotd				PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701 "701" _null_ _null_ _null_ _null_ _null_ dcotd _null_ _null_ _null_ ));
+ DESCR("cotangent, degrees");
+ 
  DATA(insert OID = 1608 (  degrees			PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701 "701" _null_ _null_ _null_ _null_ _null_ degrees _null_ _null_ _null_ ));
  DESCR("radians to degrees");
  DATA(insert OID = 1609 (  radians			PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 701 "701" _null_ _null_ _null_ _null_ _null_ radians _null_ _null_ _null_ ));
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
new file mode 100644
index b35d206..75f1d20
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum dcos(PG_FUNCTION_ARGS);
*** 407,412 ****
--- 407,420 ----
  extern Datum dcot(PG_FUNCTION_ARGS);
  extern Datum dsin(PG_FUNCTION_ARGS);
  extern Datum dtan(PG_FUNCTION_ARGS);
+ extern Datum dacosd(PG_FUNCTION_ARGS);
+ extern Datum dasind(PG_FUNCTION_ARGS);
+ extern Datum datand(PG_FUNCTION_ARGS);
+ extern Datum datan2d(PG_FUNCTION_ARGS);
+ extern Datum dcosd(PG_FUNCTION_ARGS);
+ extern Datum dcotd(PG_FUNCTION_ARGS);
+ extern Datum dsind(PG_FUNCTION_ARGS);
+ extern Datum dtand(PG_FUNCTION_ARGS);
  extern Datum degrees(PG_FUNCTION_ARGS);
  extern Datum dpi(PG_FUNCTION_ARGS);
  extern Datum radians(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/float8-exp-three-digits-win32.out b/src/test/regress/expected/float8-exp-three-digits-win32.out
new file mode 100644
index 2dd648d..6891ee0
*** a/src/test/regress/expected/float8-exp-three-digits-win32.out
--- b/src/test/regress/expected/float8-exp-three-digits-win32.out
*************** SELECT '' AS five, * FROM FLOAT8_TBL;
*** 444,446 ****
--- 444,523 ----
        | -1.2345678901234e-200
  (5 rows)
  
+ -- test exact cases for trigonometric functions in degrees
+ SELECT x,
+        CASE WHEN sind(x) IN (-1,-0.5,0,0.5,1) THEN sind(x) END AS sind,
+        CASE WHEN cosd(x) IN (-1,-0.5,0,0.5,1) THEN cosd(x) END AS cosd,
+        CASE WHEN tand(x) IN ('-Infinity'::float8,-1,0,
+                              1,'Infinity'::float8) THEN tand(x) END AS tand,
+        CASE WHEN cotd(x) IN ('-Infinity'::float8,-1,0,
+                              1,'Infinity'::float8) THEN cotd(x) END AS cotd
+ FROM generate_series(0, 360, 15) AS t(x);
+   x  | sind | cosd |   tand    |   cotd    
+ -----+------+------+-----------+-----------
+    0 |    0 |    1 |         0 |  Infinity
+   15 |      |      |           |          
+   30 |  0.5 |      |           |          
+   45 |      |      |         1 |         1
+   60 |      |  0.5 |           |          
+   75 |      |      |           |          
+   90 |    1 |    0 |  Infinity |         0
+  105 |      |      |           |          
+  120 |      | -0.5 |           |          
+  135 |      |      |        -1 |        -1
+  150 |  0.5 |      |           |          
+  165 |      |      |           |          
+  180 |    0 |   -1 |        -0 | -Infinity
+  195 |      |      |           |          
+  210 | -0.5 |      |           |          
+  225 |      |      |         1 |         1
+  240 |      | -0.5 |           |          
+  255 |      |      |           |          
+  270 |   -1 |    0 | -Infinity |        -0
+  285 |      |      |           |          
+  300 |      |  0.5 |           |          
+  315 |      |      |        -1 |        -1
+  330 | -0.5 |      |           |          
+  345 |      |      |           |          
+  360 |    0 |    1 |         0 |  Infinity
+ (25 rows)
+ 
+ SELECT x,
+        CASE WHEN asind(x) IN (-90,-30,0,30,90) THEN asind(x) END AS asind,
+        CASE WHEN acosd(x) IN (0,60,90,120,180) THEN acosd(x) END AS acosd,
+        CASE WHEN atand(x) IN (-45,0,45) THEN atand(x) END AS atand
+ FROM (VALUES (-1), (-0.5), (0), (0.5), (1)) AS t(x);
+   x   | asind | acosd | atand 
+ ------+-------+-------+-------
+    -1 |   -90 |   180 |   -45
+  -0.5 |   -30 |   120 |      
+     0 |     0 |    90 |     0
+   0.5 |    30 |    60 |      
+     1 |    90 |     0 |    45
+ (5 rows)
+ 
+ SELECT atand('-Infinity'::float8) = -90;
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ SELECT atand('Infinity'::float8) = 90;
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ SELECT x, y,
+        CASE WHEN atan2d(y, x) IN (-90,0,90,180) THEN atan2d(y, x) END AS atan2d
+ FROM (SELECT 10*cosd(a), 10*sind(a)
+       FROM generate_series(0, 360, 90) AS t(a)) AS t(x,y);
+   x  |  y  | atan2d 
+ -----+-----+--------
+   10 |   0 |      0
+    0 |  10 |     90
+  -10 |   0 |    180
+    0 | -10 |    -90
+   10 |   0 |      0
+ (5 rows)
+ 
diff --git a/src/test/regress/expected/float8-small-is-zero.out b/src/test/regress/expected/float8-small-is-zero.out
new file mode 100644
index 5da7433..e158e70
*** a/src/test/regress/expected/float8-small-is-zero.out
--- b/src/test/regress/expected/float8-small-is-zero.out
*************** SELECT '' AS five, * FROM FLOAT8_TBL;
*** 442,444 ****
--- 442,521 ----
        | -1.2345678901234e-200
  (5 rows)
  
+ -- test exact cases for trigonometric functions in degrees
+ SELECT x,
+        CASE WHEN sind(x) IN (-1,-0.5,0,0.5,1) THEN sind(x) END AS sind,
+        CASE WHEN cosd(x) IN (-1,-0.5,0,0.5,1) THEN cosd(x) END AS cosd,
+        CASE WHEN tand(x) IN ('-Infinity'::float8,-1,0,
+                              1,'Infinity'::float8) THEN tand(x) END AS tand,
+        CASE WHEN cotd(x) IN ('-Infinity'::float8,-1,0,
+                              1,'Infinity'::float8) THEN cotd(x) END AS cotd
+ FROM generate_series(0, 360, 15) AS t(x);
+   x  | sind | cosd |   tand    |   cotd    
+ -----+------+------+-----------+-----------
+    0 |    0 |    1 |         0 |  Infinity
+   15 |      |      |           |          
+   30 |  0.5 |      |           |          
+   45 |      |      |         1 |         1
+   60 |      |  0.5 |           |          
+   75 |      |      |           |          
+   90 |    1 |    0 |  Infinity |         0
+  105 |      |      |           |          
+  120 |      | -0.5 |           |          
+  135 |      |      |        -1 |        -1
+  150 |  0.5 |      |           |          
+  165 |      |      |           |          
+  180 |    0 |   -1 |        -0 | -Infinity
+  195 |      |      |           |          
+  210 | -0.5 |      |           |          
+  225 |      |      |         1 |         1
+  240 |      | -0.5 |           |          
+  255 |      |      |           |          
+  270 |   -1 |    0 | -Infinity |        -0
+  285 |      |      |           |          
+  300 |      |  0.5 |           |          
+  315 |      |      |        -1 |        -1
+  330 | -0.5 |      |           |          
+  345 |      |      |           |          
+  360 |    0 |    1 |         0 |  Infinity
+ (25 rows)
+ 
+ SELECT x,
+        CASE WHEN asind(x) IN (-90,-30,0,30,90) THEN asind(x) END AS asind,
+        CASE WHEN acosd(x) IN (0,60,90,120,180) THEN acosd(x) END AS acosd,
+        CASE WHEN atand(x) IN (-45,0,45) THEN atand(x) END AS atand
+ FROM (VALUES (-1), (-0.5), (0), (0.5), (1)) AS t(x);
+   x   | asind | acosd | atand 
+ ------+-------+-------+-------
+    -1 |   -90 |   180 |   -45
+  -0.5 |   -30 |   120 |      
+     0 |     0 |    90 |     0
+   0.5 |    30 |    60 |      
+     1 |    90 |     0 |    45
+ (5 rows)
+ 
+ SELECT atand('-Infinity'::float8) = -90;
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ SELECT atand('Infinity'::float8) = 90;
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ SELECT x, y,
+        CASE WHEN atan2d(y, x) IN (-90,0,90,180) THEN atan2d(y, x) END AS atan2d
+ FROM (SELECT 10*cosd(a), 10*sind(a)
+       FROM generate_series(0, 360, 90) AS t(a)) AS t(x,y);
+   x  |  y  | atan2d 
+ -----+-----+--------
+   10 |   0 |      0
+    0 |  10 |     90
+  -10 |   0 |    180
+    0 | -10 |    -90
+   10 |   0 |      0
+ (5 rows)
+ 
diff --git a/src/test/regress/expected/float8-small-is-zero_1.out b/src/test/regress/expected/float8-small-is-zero_1.out
new file mode 100644
index 530842e..42e50a0
*** a/src/test/regress/expected/float8-small-is-zero_1.out
--- b/src/test/regress/expected/float8-small-is-zero_1.out
*************** SELECT '' AS five, * FROM FLOAT8_TBL;
*** 442,444 ****
--- 442,521 ----
        | -1.2345678901234e-200
  (5 rows)
  
+ -- test exact cases for trigonometric functions in degrees
+ SELECT x,
+        CASE WHEN sind(x) IN (-1,-0.5,0,0.5,1) THEN sind(x) END AS sind,
+        CASE WHEN cosd(x) IN (-1,-0.5,0,0.5,1) THEN cosd(x) END AS cosd,
+        CASE WHEN tand(x) IN ('-Infinity'::float8,-1,0,
+                              1,'Infinity'::float8) THEN tand(x) END AS tand,
+        CASE WHEN cotd(x) IN ('-Infinity'::float8,-1,0,
+                              1,'Infinity'::float8) THEN cotd(x) END AS cotd
+ FROM generate_series(0, 360, 15) AS t(x);
+   x  | sind | cosd |   tand    |   cotd    
+ -----+------+------+-----------+-----------
+    0 |    0 |    1 |         0 |  Infinity
+   15 |      |      |           |          
+   30 |  0.5 |      |           |          
+   45 |      |      |         1 |         1
+   60 |      |  0.5 |           |          
+   75 |      |      |           |          
+   90 |    1 |    0 |  Infinity |         0
+  105 |      |      |           |          
+  120 |      | -0.5 |           |          
+  135 |      |      |        -1 |        -1
+  150 |  0.5 |      |           |          
+  165 |      |      |           |          
+  180 |    0 |   -1 |        -0 | -Infinity
+  195 |      |      |           |          
+  210 | -0.5 |      |           |          
+  225 |      |      |         1 |         1
+  240 |      | -0.5 |           |          
+  255 |      |      |           |          
+  270 |   -1 |    0 | -Infinity |        -0
+  285 |      |      |           |          
+  300 |      |  0.5 |           |          
+  315 |      |      |        -1 |        -1
+  330 | -0.5 |      |           |          
+  345 |      |      |           |          
+  360 |    0 |    1 |         0 |  Infinity
+ (25 rows)
+ 
+ SELECT x,
+        CASE WHEN asind(x) IN (-90,-30,0,30,90) THEN asind(x) END AS asind,
+        CASE WHEN acosd(x) IN (0,60,90,120,180) THEN acosd(x) END AS acosd,
+        CASE WHEN atand(x) IN (-45,0,45) THEN atand(x) END AS atand
+ FROM (VALUES (-1), (-0.5), (0), (0.5), (1)) AS t(x);
+   x   | asind | acosd | atand 
+ ------+-------+-------+-------
+    -1 |   -90 |   180 |   -45
+  -0.5 |   -30 |   120 |      
+     0 |     0 |    90 |     0
+   0.5 |    30 |    60 |      
+     1 |    90 |     0 |    45
+ (5 rows)
+ 
+ SELECT atand('-Infinity'::float8) = -90;
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ SELECT atand('Infinity'::float8) = 90;
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ SELECT x, y,
+        CASE WHEN atan2d(y, x) IN (-90,0,90,180) THEN atan2d(y, x) END AS atan2d
+ FROM (SELECT 10*cosd(a), 10*sind(a)
+       FROM generate_series(0, 360, 90) AS t(a)) AS t(x,y);
+   x  |  y  | atan2d 
+ -----+-----+--------
+   10 |   0 |      0
+    0 |  10 |     90
+  -10 |   0 |    180
+    0 | -10 |    -90
+   10 |   0 |      0
+ (5 rows)
+ 
diff --git a/src/test/regress/expected/float8.out b/src/test/regress/expected/float8.out
new file mode 100644
index 6221538..b77b34f
*** a/src/test/regress/expected/float8.out
--- b/src/test/regress/expected/float8.out
*************** SELECT '' AS five, * FROM FLOAT8_TBL;
*** 444,446 ****
--- 444,523 ----
        | -1.2345678901234e-200
  (5 rows)
  
+ -- test exact cases for trigonometric functions in degrees
+ SELECT x,
+        CASE WHEN sind(x) IN (-1,-0.5,0,0.5,1) THEN sind(x) END AS sind,
+        CASE WHEN cosd(x) IN (-1,-0.5,0,0.5,1) THEN cosd(x) END AS cosd,
+        CASE WHEN tand(x) IN ('-Infinity'::float8,-1,0,
+                              1,'Infinity'::float8) THEN tand(x) END AS tand,
+        CASE WHEN cotd(x) IN ('-Infinity'::float8,-1,0,
+                              1,'Infinity'::float8) THEN cotd(x) END AS cotd
+ FROM generate_series(0, 360, 15) AS t(x);
+   x  | sind | cosd |   tand    |   cotd    
+ -----+------+------+-----------+-----------
+    0 |    0 |    1 |         0 |  Infinity
+   15 |      |      |           |          
+   30 |  0.5 |      |           |          
+   45 |      |      |         1 |         1
+   60 |      |  0.5 |           |          
+   75 |      |      |           |          
+   90 |    1 |    0 |  Infinity |         0
+  105 |      |      |           |          
+  120 |      | -0.5 |           |          
+  135 |      |      |        -1 |        -1
+  150 |  0.5 |      |           |          
+  165 |      |      |           |          
+  180 |    0 |   -1 |        -0 | -Infinity
+  195 |      |      |           |          
+  210 | -0.5 |      |           |          
+  225 |      |      |         1 |         1
+  240 |      | -0.5 |           |          
+  255 |      |      |           |          
+  270 |   -1 |    0 | -Infinity |        -0
+  285 |      |      |           |          
+  300 |      |  0.5 |           |          
+  315 |      |      |        -1 |        -1
+  330 | -0.5 |      |           |          
+  345 |      |      |           |          
+  360 |    0 |    1 |         0 |  Infinity
+ (25 rows)
+ 
+ SELECT x,
+        CASE WHEN asind(x) IN (-90,-30,0,30,90) THEN asind(x) END AS asind,
+        CASE WHEN acosd(x) IN (0,60,90,120,180) THEN acosd(x) END AS acosd,
+        CASE WHEN atand(x) IN (-45,0,45) THEN atand(x) END AS atand
+ FROM (VALUES (-1), (-0.5), (0), (0.5), (1)) AS t(x);
+   x   | asind | acosd | atand 
+ ------+-------+-------+-------
+    -1 |   -90 |   180 |   -45
+  -0.5 |   -30 |   120 |      
+     0 |     0 |    90 |     0
+   0.5 |    30 |    60 |      
+     1 |    90 |     0 |    45
+ (5 rows)
+ 
+ SELECT atand('-Infinity'::float8) = -90;
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ SELECT atand('Infinity'::float8) = 90;
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ SELECT x, y,
+        CASE WHEN atan2d(y, x) IN (-90,0,90,180) THEN atan2d(y, x) END AS atan2d
+ FROM (SELECT 10*cosd(a), 10*sind(a)
+       FROM generate_series(0, 360, 90) AS t(a)) AS t(x,y);
+   x  |  y  | atan2d 
+ -----+-----+--------
+   10 |   0 |      0
+    0 |  10 |     90
+  -10 |   0 |    180
+    0 | -10 |    -90
+   10 |   0 |      0
+ (5 rows)
+ 
diff --git a/src/test/regress/sql/float8.sql b/src/test/regress/sql/float8.sql
new file mode 100644
index 92a574a..45a484b
*** a/src/test/regress/sql/float8.sql
--- b/src/test/regress/sql/float8.sql
*************** INSERT INTO FLOAT8_TBL(f1) VALUES ('-1.2
*** 167,169 ****
--- 167,193 ----
  INSERT INTO FLOAT8_TBL(f1) VALUES ('-1.2345678901234e-200');
  
  SELECT '' AS five, * FROM FLOAT8_TBL;
+ 
+ -- test exact cases for trigonometric functions in degrees
+ SELECT x,
+        CASE WHEN sind(x) IN (-1,-0.5,0,0.5,1) THEN sind(x) END AS sind,
+        CASE WHEN cosd(x) IN (-1,-0.5,0,0.5,1) THEN cosd(x) END AS cosd,
+        CASE WHEN tand(x) IN ('-Infinity'::float8,-1,0,
+                              1,'Infinity'::float8) THEN tand(x) END AS tand,
+        CASE WHEN cotd(x) IN ('-Infinity'::float8,-1,0,
+                              1,'Infinity'::float8) THEN cotd(x) END AS cotd
+ FROM generate_series(0, 360, 15) AS t(x);
+ 
+ SELECT x,
+        CASE WHEN asind(x) IN (-90,-30,0,30,90) THEN asind(x) END AS asind,
+        CASE WHEN acosd(x) IN (0,60,90,120,180) THEN acosd(x) END AS acosd,
+        CASE WHEN atand(x) IN (-45,0,45) THEN atand(x) END AS atand
+ FROM (VALUES (-1), (-0.5), (0), (0.5), (1)) AS t(x);
+ 
+ SELECT atand('-Infinity'::float8) = -90;
+ SELECT atand('Infinity'::float8) = 90;
+ 
+ SELECT x, y,
+        CASE WHEN atan2d(y, x) IN (-90,0,90,180) THEN atan2d(y, x) END AS atan2d
+ FROM (SELECT 10*cosd(a), 10*sind(a)
+       FROM generate_series(0, 360, 90) AS t(a)) AS t(x,y);
#54Michael Paquier
michael.paquier@gmail.com
In reply to: Dean Rasheed (#53)
Re: Proposal: Trigonometric functions in degrees

On Mon, Jan 18, 2016 at 5:01 AM, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

On 30 November 2015 at 14:11, Tom Lane <tgl@sss.pgh.pa.us> wrote:

FWIW, I think that probably the best course of action is to go ahead
and install POSIX-compliant error checking in the existing functions
too. POSIX:2008 is quite clear about this:

: An application wishing to check for error situations should set errno to
: zero and call feclearexcept(FE_ALL_EXCEPT) before calling these
: functions. On return, if errno is non-zero or fetestexcept(FE_INVALID |
: FE_DIVBYZERO | FE_OVERFLOW | FE_UNDERFLOW) is non-zero, an error has
: occurred.

Looking at this again, I think it makes sense to make the Inf/NaN
handling of these functions platform-independent. POSIX.1-2008 is
pretty explicit about how they ought to behave, which is different
from the current behaviour on either Linux or Windows:

sin(Inf):
POSIX: domain error
Linux: NaN
Windows: ERROR: input is out of range

OSX: NaN

asin(Inf):
POSIX: domain error
Linux: ERROR: input is out of range
Windows: ERROR: input is out of range

OSX: NaN

sin(NaN):
POSIX: NaN
Linux: NaN
Windows: ERROR: input is out of range

OSX: NaN

asin(NaN):
POSIX: NaN
Linux: NaN
Windows: ERROR: input is out of range

OSX: NaN.

I tried using feclearexcept + fetestexcept as recommended by
POSIX.1-2008, and that does indeed make the above examples compliant
on my linux box. Unfortunately it introduces new errors -- asin starts
generating FE_UNDERFLOW errors for numbers that are really not that
small, such as asin(1e-9), although the function still returns the
correct answer. A bigger problem though is that these are C99
functions and so won't necessarily be available on all supported
platforms.

So I think that a simpler answer is to just to add explicit tests for
these inputs and avoid relying on errno, at least for the inverse
functions, which have pretty clear constraints on their allowed
inputs. For the forward functions, I'm not sure if there are some
platforms on which large but finite inputs might generate errors, so I
think it's safest to keep the existing errno checks as well just in
case.

Thanks for this investigation. That's really inconsistent...

Attached are patches for this and the new functions in degrees, now
with POSIX compatible Inf/NaN handling.

The first patch looks fine to me, a nitpick:
+ /* Be sure to throw an error if the input is infinite --- see dcos */
s/dcos/docs

For patch 2, another nitpick :)
+ return ( sin(x * (M_PI / 180.0)) / sin(30.0 * (M_PI / 180.0)) ) / 2.0;
parenthesis format looks incorrect, too many spaces at the border.

Except those things the second patch looks good to me as well. Let's
have a committer look at it.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#55Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Michael Paquier (#54)
Re: Proposal: Trigonometric functions in degrees

On 19 January 2016 at 06:32, Michael Paquier <michael.paquier@gmail.com> wrote:

The first patch looks fine to me, a nitpick:
+ /* Be sure to throw an error if the input is infinite --- see dcos */
s/dcos/docs

No, I meant dcos the function there. I would normally write that as
dcos() to indicate that it is a function, but I thought the convention
here was to omit the parentheses after function names. Looking again,
I see several examples of function names with parentheses in comments,
so they could be added here, or the comment could be written a
different way. I'm happy to leave that to the committer's discretion.

For patch 2, another nitpick :)
+ return ( sin(x * (M_PI / 180.0)) / sin(30.0 * (M_PI / 180.0)) ) / 2.0;
parenthesis format looks incorrect, too many spaces at the border.

Except those things the second patch looks good to me as well. Let's
have a committer look at it.

OK. Thanks for looking at this.

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#56Tom Lane
tgl@sss.pgh.pa.us
In reply to: Dean Rasheed (#53)
Re: Proposal: Trigonometric functions in degrees

Dean Rasheed <dean.a.rasheed@gmail.com> writes:

I tried using feclearexcept + fetestexcept as recommended by
POSIX.1-2008, and that does indeed make the above examples compliant
on my linux box. Unfortunately it introduces new errors -- asin starts
generating FE_UNDERFLOW errors for numbers that are really not that
small, such as asin(1e-9), although the function still returns the
correct answer. A bigger problem though is that these are C99
functions and so won't necessarily be available on all supported
platforms.

The latter issue could be dealt with via configure tests, but I agree that
if we get new error conditions we weren't exactly looking for, it would
not be a net improvement. I think ensuring that the Inf and NaN cases are
platform-independent is already a good step forward, so we can stop there
for now.

Attached are patches for this and the new functions in degrees, now
with POSIX compatible Inf/NaN handling.

Pushed with minor, mostly cosmetic fixes. I did renumber the function
OIDs to be closer to the original trig functions' numbers, using some
OIDs that were freed up by the recent index AM API change.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#57Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tom Lane (#56)
Re: Proposal: Trigonometric functions in degrees

I wrote:

Dean Rasheed <dean.a.rasheed@gmail.com> writes:

Attached are patches for this and the new functions in degrees, now
with POSIX compatible Inf/NaN handling.

Pushed with minor, mostly cosmetic fixes.

So the early returns from the buildfarm aren't very good:

* tern/sungazer isn't getting exactly 0.5 from sind(30).

* narwhal isn't getting exactly 1 from tand(45) or cotd(45).
It also is producing "0" not "-0" from tand(180) and cotd(270).

* gaur likewise is getting "0" not "-0" from tand(180) and cotd(270).

The tern/sungazer result implies that this:

return (sin(x * (M_PI / 180.0)) / sin(30.0 * (M_PI / 180.0))) / 2.0;

is not producing exactly 0.5, which means that the two sin() calls aren't
producing identical results, which I suspect is best explained by the
theory that the compiler is rearranging 30.0 * (M_PI / 180.0) into
(30.0 * M_PI) / 180.0, and getting a slightly different number that way.

I think we could fix that by replacing (M_PI / 180.0) by a hard-wired
constant (computed to say 20 digits or so). I'd be inclined to do that
throughout the file for consistency; however, in principle such a change
might affect existing results from the radians() and degrees() functions.

The tand(45) problem doesn't seem especially surprising, because we're
really not making any effort to ensure that that result is exact.
A brute-force way to fix it would be to divide
(sind_q1(arg1) / cosd_q1(arg1)) by (sind_q1(45.0) / cosd_q1(45.0))
but that seems rather expensive, and possibly subject to the compiler
deciding to reorder the arithmetic operations. I wonder if there's a
smarter way.

As for the minus-zero issues, I doubt that (a) there is a basis for
claiming that minus-zero is more correct than plain zero, or (b) the user
community for this feature really wants minus-zero results anyhow.
I propose getting rid of minus-zero outputs from tand and cotd by dint
of code like

if (result == 0.0)
result = 0.0;

Thoughts?

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#58Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tom Lane (#57)
Re: Proposal: Trigonometric functions in degrees

I wrote:

So the early returns from the buildfarm aren't very good:
* tern/sungazer isn't getting exactly 0.5 from sind(30).

The tern/sungazer result implies that this:

return (sin(x * (M_PI / 180.0)) / sin(30.0 * (M_PI / 180.0))) / 2.0;

is not producing exactly 0.5, which means that the two sin() calls aren't
producing identical results, which I suspect is best explained by the
theory that the compiler is rearranging 30.0 * (M_PI / 180.0) into
(30.0 * M_PI) / 180.0, and getting a slightly different number that way.

I think we could fix that by replacing (M_PI / 180.0) by a hard-wired
constant (computed to say 20 digits or so).

So I pushed that, and tern/sungazer are still failing. Noah, could you
trace through that and see exactly where it's going off the rails?

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#59Peter Eisentraut
peter_e@gmx.net
In reply to: Tom Lane (#58)
Re: Proposal: Trigonometric functions in degrees

On 1/23/16 12:08 PM, Tom Lane wrote:

So I pushed that, and tern/sungazer are still failing. Noah, could you
trace through that and see exactly where it's going off the rails?

I'm still getting a failure in float8 on OS X after commit 73193d8:

@@ -490,9 +490,9 @@
   x   | asind | acosd | atand
 ------+-------+-------+-------
    -1 |   -90 |   180 |   -45
- -0.5 |   -30 |   120 |
+ -0.5 |       |   120 |
     0 |     0 |    90 |     0
-  0.5 |    30 |    60 |
+  0.5 |       |       |
     1 |    90 |     0 |    45

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#60Tom Lane
tgl@sss.pgh.pa.us
In reply to: Peter Eisentraut (#59)
Re: Proposal: Trigonometric functions in degrees

Peter Eisentraut <peter_e@gmx.net> writes:

On 1/23/16 12:08 PM, Tom Lane wrote:

So I pushed that, and tern/sungazer are still failing. Noah, could you
trace through that and see exactly where it's going off the rails?

I'm still getting a failure in float8 on OS X after commit 73193d8:

Weird, because my OS X critters are all happy. Which OS X and compiler
version, exactly? Any special compile flags?

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#61Noah Misch
noah@leadboat.com
In reply to: Tom Lane (#58)
Re: Proposal: Trigonometric functions in degrees

On Sat, Jan 23, 2016 at 12:08:40PM -0500, Tom Lane wrote:

I wrote:

So the early returns from the buildfarm aren't very good:
* tern/sungazer isn't getting exactly 0.5 from sind(30).

The tern/sungazer result implies that this:

return (sin(x * (M_PI / 180.0)) / sin(30.0 * (M_PI / 180.0))) / 2.0;

is not producing exactly 0.5, which means that the two sin() calls aren't
producing identical results, which I suspect is best explained by the
theory that the compiler is rearranging 30.0 * (M_PI / 180.0) into
(30.0 * M_PI) / 180.0, and getting a slightly different number that way.

I think we could fix that by replacing (M_PI / 180.0) by a hard-wired
constant (computed to say 20 digits or so).

So I pushed that, and tern/sungazer are still failing. Noah, could you
trace through that and see exactly where it's going off the rails?

The second sin() is a constant, so gcc computes it immediately but sends the
first sin() to libm. The libm sin() is slightly more accurate. In %a
notation, AIX libm computes sin(30.0 * RADIANS_PER_DEGREE) as 0x1p-1 while gcc
computes it as 0x1.fffffffffffffp-2, a difference of one ULP. (Both "30.0 *
RADIANS_PER_DEGREE" and "30.0 * (M_PI / 180.0)" match the runtime computation
of 0x1.0c152382d7365p-1.)

To reliably produce exact answers, this code must delay all trigonometric
calculations to runtime. On sungazer, the float8 test happens to pass if I
rebuild float.c with -fno-builtin-sin; that leaves calls like acos(0.5) and
cos(60.0 * RADIANS_PER_DEGREE) unprotected, though.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#62Tom Lane
tgl@sss.pgh.pa.us
In reply to: Noah Misch (#61)
Re: Proposal: Trigonometric functions in degrees

Noah Misch <noah@leadboat.com> writes:

On Sat, Jan 23, 2016 at 12:08:40PM -0500, Tom Lane wrote:

So I pushed that, and tern/sungazer are still failing. Noah, could you
trace through that and see exactly where it's going off the rails?

The second sin() is a constant, so gcc computes it immediately but sends the
first sin() to libm. The libm sin() is slightly more accurate.

Ugh. "compile-time simplification uses different version of sin()" was
one of the theories I had in mind, but I was hoping that wasn't it
because it'd be the hardest to work around reliably. Still, I think it's
doable by caching the results of the should-be-constant subexpressions.
Will get on it.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#63Peter Eisentraut
peter_e@gmx.net
In reply to: Tom Lane (#60)
Re: Proposal: Trigonometric functions in degrees

On 1/23/16 3:05 PM, Tom Lane wrote:

Peter Eisentraut <peter_e@gmx.net> writes:

On 1/23/16 12:08 PM, Tom Lane wrote:

So I pushed that, and tern/sungazer are still failing. Noah, could you
trace through that and see exactly where it's going off the rails?

I'm still getting a failure in float8 on OS X after commit 73193d8:

Weird, because my OS X critters are all happy. Which OS X and compiler
version, exactly? Any special compile flags?

I'm using gcc 4.8. It passes with the system clang. So the explanation
is probably along the lines of what Noah has described.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#64Tom Lane
tgl@sss.pgh.pa.us
In reply to: Peter Eisentraut (#63)
Re: Proposal: Trigonometric functions in degrees

Peter Eisentraut <peter_e@gmx.net> writes:

On 1/23/16 3:05 PM, Tom Lane wrote:

Peter Eisentraut <peter_e@gmx.net> writes:

I'm still getting a failure in float8 on OS X after commit 73193d8:

Weird, because my OS X critters are all happy. Which OS X and compiler
version, exactly? Any special compile flags?

I'm using gcc 4.8. It passes with the system clang. So the explanation
is probably along the lines of what Noah has described.

Ah. Please see if what I just pushed fixes it.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#65Tom Lane
tgl@sss.pgh.pa.us
In reply to: Noah Misch (#61)
Re: Proposal: Trigonometric functions in degrees

Noah Misch <noah@leadboat.com> writes:

To reliably produce exact answers, this code must delay all trigonometric
calculations to runtime. On sungazer, the float8 test happens to pass if I
rebuild float.c with -fno-builtin-sin; that leaves calls like acos(0.5) and
cos(60.0 * RADIANS_PER_DEGREE) unprotected, though.

Either I missed something or there's another issue, because tern/sungazer
are *still* failing. This is getting annoying :-(

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#66Noah Misch
noah@leadboat.com
In reply to: Tom Lane (#65)
Re: Proposal: Trigonometric functions in degrees

On Sat, Jan 23, 2016 at 05:04:56PM -0500, Tom Lane wrote:

Noah Misch <noah@leadboat.com> writes:

To reliably produce exact answers, this code must delay all trigonometric
calculations to runtime. On sungazer, the float8 test happens to pass if I
rebuild float.c with -fno-builtin-sin; that leaves calls like acos(0.5) and
cos(60.0 * RADIANS_PER_DEGREE) unprotected, though.

Either I missed something or there's another issue, because tern/sungazer
are *still* failing. This is getting annoying :-(

sungazer's "make check" passes if I change init_degree_constants() to be
non-static. Duping gcc isn't so easy these days.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#67Tom Lane
tgl@sss.pgh.pa.us
In reply to: Noah Misch (#66)
Re: Proposal: Trigonometric functions in degrees

Noah Misch <noah@leadboat.com> writes:

On Sat, Jan 23, 2016 at 05:04:56PM -0500, Tom Lane wrote:

Either I missed something or there's another issue, because tern/sungazer
are *still* failing. This is getting annoying :-(

sungazer's "make check" passes if I change init_degree_constants() to be
non-static. Duping gcc isn't so easy these days.

Ugh. Well, at least we don't have to move it to another file, which was
going to be my next larger size of hammer.

Thanks for doing the legwork on this!

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#68Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Tom Lane (#67)
Re: Proposal: Trigonometric functions in degrees

On 23 January 2016 at 23:04, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Noah Misch <noah@leadboat.com> writes:

On Sat, Jan 23, 2016 at 05:04:56PM -0500, Tom Lane wrote:

Either I missed something or there's another issue, because tern/sungazer
are *still* failing. This is getting annoying :-(

sungazer's "make check" passes if I change init_degree_constants() to be
non-static. Duping gcc isn't so easy these days.

Ugh. Well, at least we don't have to move it to another file, which was
going to be my next larger size of hammer.

Thanks for doing the legwork on this!

Hi, I'm just now catching up on my email after being out of town and
not reading it. Thanks for looking at this and sorting out those
issues, and thank you also Noah and Peter for your investigative work.

If I understand correctly there were 2 separate issues at play here:

1). On some platforms the compiler evaluates expressions like
sin(constant) and comes up with a slightly different result than a
runtime evaluation of the expression. The compiler-evaluated result is
presumably a 64-bit IEEE float, but at runtime it may be using
extended precision for intermediate results. That may well have been
the sole contributing factor to the fact that sind(30) wasn't exactly
0.5.

2). The compiler also sometimes rearranges expressions in ways that
don't give the same result as evaluating in the order suggested by the
parentheses. I think this actually explains the failure to get exactly
1 for tand(45). For x=45, this was being computed as

cosd_0_to_60(90 - x) / cosd_0_to_60(x)

so my guess is that it was inlining cosd_0_to_60(90 - x) and
rearranging it to produce something different from cosd_0_to_60(x) for
x=45.

Looking at the new code, it's annoying how much effort was needed to
prevent the compiler messing it up. I ought to have realised that the
optimiser would be awkward for this kind of thing.

I wonder if the same could have been achieved by disabling
optimisation and inlining in those low-level functions, and also
wrapping sin(x * RADIANS_PER_DEGREE) in a similar non-inlinable,
non-optimised function to force it to be executed at runtime when
passed a constant. That would probably have made them significantly
slower though, whereas the new code benefits from various pre-computed
expressions.

Thanks again for fixing this.

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#69Tom Lane
tgl@sss.pgh.pa.us
In reply to: Dean Rasheed (#68)
Re: Proposal: Trigonometric functions in degrees

Dean Rasheed <dean.a.rasheed@gmail.com> writes:

If I understand correctly there were 2 separate issues at play here:

1). On some platforms the compiler evaluates expressions like
sin(constant) and comes up with a slightly different result than a
runtime evaluation of the expression. The compiler-evaluated result
is presumably a 64-bit IEEE float, but at runtime it may be using
extended precision for intermediate results.

If I've not lost track of the bidding, both of the cases where we saw
that involved gcc on a platform where it's not the native (vendor
supplied) compiler. So my guess is that gcc was using a non-native
libm to do the pre-evaluation of sin(). It does not seem likely that
the gcc boys would intentionally do pre-evaluation differently from
run-time evaluation, but they could get burnt by what would arguably
be a build error in that particular copy of gcc. Cross-compilation
would be another way to hit the same type of hazard.

That may well have been the sole contributing factor to the fact that
sind(30) wasn't exactly 0.5.

Yeah. I am not sure whether the RADIANS_PER_DEGREE change fixed anything
or not, though I am not tempted to undo it; it may save us on some other
platform not currently represented in the buildfarm.

2). The compiler also sometimes rearranges expressions in ways that
don't give the same result as evaluating in the order suggested by the
parentheses. I think this actually explains the failure to get exactly
1 for tand(45). For x=45, this was being computed as
cosd_0_to_60(90 - x) / cosd_0_to_60(x)
so my guess is that it was inlining cosd_0_to_60(90 - x) and
rearranging it to produce something different from cosd_0_to_60(x) for
x=45.

Oh, interesting point. The inlining would have produced a subexpression
like

cos((90 - x) * RADIANS_PER_DEGREE)

For x=45, the result of 90-x would have been exact, so it's not obvious
where any change in results would have crept in --- but if the compiler
then tried to simplify to

cos((90 * RADIANS_PER_DEGREE) - (x * RADIANS_PER_DEGREE))

that could definitely change the roundoff behavior. OTOH, it's not
very clear why gcc would have done that; it saves no operations.
It'd be interesting to look at the produced assembly code on narwhal.

I wonder if the same could have been achieved by disabling
optimisation and inlining in those low-level functions, and also
wrapping sin(x * RADIANS_PER_DEGREE) in a similar non-inlinable,
non-optimised function to force it to be executed at runtime when
passed a constant.

I considered that; in particular -ffloat-store would have helped with the
wider-intermediate-results problem, and indeed we might still be forced
into using that. I would rather not fall back to adding more
compiler-specific flags though. If we go that way we'll likely need a
custom fix for every new compiler we try to use.

Meanwhile, just when you thought it was safe to go back in the water,
cockatiel is still failing. It has the cos(60) != 0.5 problem, which
IIRC was exhibited by no other critter. Looking at the code,

cosd_0_to_60(double x)
{
return 1.0 - ((1.0 - cos(x * RADIANS_PER_DEGREE)) / one_minus_cos_60) / 2.0;
}

what seems likely is that the "1 - cos()" subtraction is being done in
a wider-than-double float register, which i686 does have, and producing
a different result than what was stored in one_minus_cos_60.

Perhaps we can fix this by rewriting as

float8 numerator = 1.0 - cos(x * RADIANS_PER_DEGREE);
return 1.0 - (numerator / one_minus_cos_60) / 2.0;

cockatiel's compiler does recognize -fexcess-precision=standard, and
my understanding of that is that the result put into "numerator" will
be rounded to double width, so that it should then match
"one_minus_cos_60".

Another idea would be to change the cache variable to just "cos_60" and
write "(1.0 - cos_60)" in the denominator --- but then we'd just be hoping
that the compiler does both subtractions the same way, which doesn't seem
necessarily guaranteed. Worse, I believe the 8087 has an FCOS instruction
which might deliver a wider-than-double result, so that maybe the problem
is not so much with the subtraction as with when rounding of the cos()
result happens. The code I show above seems more likely to match the
way one_minus_cos_60 is computed.

I'll go try it, though I guess we won't see results till tomorrow.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#70Peter Eisentraut
peter_e@gmx.net
In reply to: Tom Lane (#64)
Re: Proposal: Trigonometric functions in degrees

On 1/23/16 4:18 PM, Tom Lane wrote:

Peter Eisentraut <peter_e@gmx.net> writes:

On 1/23/16 3:05 PM, Tom Lane wrote:

Peter Eisentraut <peter_e@gmx.net> writes:

I'm still getting a failure in float8 on OS X after commit 73193d8:

Weird, because my OS X critters are all happy. Which OS X and compiler
version, exactly? Any special compile flags?

I'm using gcc 4.8. It passes with the system clang. So the explanation
is probably along the lines of what Noah has described.

Ah. Please see if what I just pushed fixes it.

Works now. Thanks.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#71Michael Paquier
michael.paquier@gmail.com
In reply to: Tom Lane (#69)
Re: Proposal: Trigonometric functions in degrees

On Mon, Jan 25, 2016 at 2:34 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Perhaps we can fix this by rewriting as

float8 numerator = 1.0 - cos(x * RADIANS_PER_DEGREE);
return 1.0 - (numerator / one_minus_cos_60) / 2.0;

cockatiel's compiler does recognize -fexcess-precision=standard, and
my understanding of that is that the result put into "numerator" will
be rounded to double width, so that it should then match
"one_minus_cos_60".

Another idea would be to change the cache variable to just "cos_60" and
write "(1.0 - cos_60)" in the denominator --- but then we'd just be hoping
that the compiler does both subtractions the same way, which doesn't seem
necessarily guaranteed. Worse, I believe the 8087 has an FCOS instruction
which might deliver a wider-than-double result, so that maybe the problem
is not so much with the subtraction as with when rounding of the cos()
result happens. The code I show above seems more likely to match the
way one_minus_cos_60 is computed.

(Sorry for showing up after the storm)
The fix is working correctly, using gcc's i686-pc-cygwin on cygwin32
the regression does not show up anymore
after 0034757.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#72Piotr Stefaniak
postgres@piotr-stefaniak.me
In reply to: Noah Misch (#61)
Re: Proposal: Trigonometric functions in degrees

These changes from 65abaab547a5758b0d6d92df4af1663bb47d545f

- result = sign * cosd_q1(arg1) / sind_q1(arg1);
+ result = sign * ((cosd_q1(arg1) / sind_q1(arg1)) / cot_45);

and

- result = sign * sind_q1(arg1) / cosd_q1(arg1);
+ result = sign * ((sind_q1(arg1) / cosd_q1(arg1)) / tan_45);

both introduce division by zero, don't they?

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#73Michael Paquier
michael.paquier@gmail.com
In reply to: Piotr Stefaniak (#72)
Re: Proposal: Trigonometric functions in degrees

On Sun, Jan 31, 2016 at 9:01 PM, Piotr Stefaniak
<postgres@piotr-stefaniak.me> wrote:

- result = sign * cosd_q1(arg1) / sind_q1(arg1);
+ result = sign * ((cosd_q1(arg1) / sind_q1(arg1)) / cot_45);

and

- result = sign * sind_q1(arg1) / cosd_q1(arg1);
+ result = sign * ((sind_q1(arg1) / cosd_q1(arg1)) / tan_45);

both introduce division by zero, don't they?

Per IEEE 754, division by 0 for a double results in Nan or +/-Inf, so
that's actually correct.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#74Piotr Stefaniak
postgres@piotr-stefaniak.me
In reply to: Michael Paquier (#73)
Re: Proposal: Trigonometric functions in degrees

On 01/31/2016 01:23 PM, Michael Paquier wrote:

Per IEEE 754, division by 0 for a double results in Nan or +/-Inf, so
that's actually correct.

I didn't know that. I guess that in practice that is OK and the case is
closed.

Interestingly to me, that assumption appears to rely on the C
implementation complying to IEC 60559, in which case C99 lets the
implementation signal that by defining the __STDC_IEC_559__ macro. C89
doesn't seem to mention any of this.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#75Tom Lane
tgl@sss.pgh.pa.us
In reply to: Piotr Stefaniak (#72)
Re: Proposal: Trigonometric functions in degrees

Piotr Stefaniak <postgres@piotr-stefaniak.me> writes:

These changes from 65abaab547a5758b0d6d92df4af1663bb47d545f
- result = sign * cosd_q1(arg1) / sind_q1(arg1);
+ result = sign * ((cosd_q1(arg1) / sind_q1(arg1)) / cot_45);

and

- result = sign * sind_q1(arg1) / cosd_q1(arg1);
+ result = sign * ((sind_q1(arg1) / cosd_q1(arg1)) / tan_45);

both introduce division by zero, don't they?

Huh? cot_45 and tan_45 are fixed values that should be very close to 1.
Or were you complaining that the potential div by 0 existed beforehand?

It's possible that we should check for sind_q1(arg1) or cosd_q1(arg1)
being zero before we try the divide, and substitute get_float8_infinity()
instead. But the regression tests exercise this case, and none of the
buildfarm members complained, so I'm a bit disinclined to add code for
that purpose. If anyone does report regression test failure here, we can
revisit the issue then.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers