You could always do e.g. math.sin(math.degress(radians)) and so forth...
_______________________________________________
Python-ideas mailing list
Python...@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/
I suggest adding degrees version of the trigonometric functions in the math module.
To make it so that sindeg/cosdeg of multiples of 90 come out exact is
probably easiest to do by doing the angle reduction in degrees (so the
nice precise angles stay as nice precise angles) and then either adjust
the final computation formulas for degrees, or convert the angle to
radians and let the fundamental routine do the small angle computation.
While we are at it, it might be worth thinking if it might make sense to
also define a set of functions using circles as a unit (90 degrees =
0.25, one whole revolution = 1)
--
Richard Damon
6.123233995736766e-17
>>>
is good enough for government work, including at the local public high
school.
But that's not a separate issue, that's precisely one of the motives for
having dedicated trig functions for degrees.
sind(45) (or dsin(45), as I would prefer) could (in principle) return
the closest possible float to sqrt(2)/2, which sin(45*DEG) does not do:
py> DEG = 2 * math.pi / 360
py> math.sin(45*DEG) == math.sqrt(2)/2
False
Likewise, we'd expect cosd(90) to return zero, not something not-quite
zero:
py> math.cos(90*DEG)
6.123031769111886e-17
That's how it works in Julia:
julia> sind(45) == sqrt(2)/2
true
julia> cosd(90)
0.0
and I'd expect no less here. If we can't do that, there probably
wouldn't be much point in the exercise.
from rational_trig import cos
if cos(90*degrees) == 0:
print("yay!")
It is probably slow as molasses, but maybe good enough for a
teaching environment?
Richard Damon
> But if there are both sin and dsin, and you ask about the difference
> between them, the obvious answer would be that one takes radians and the
> other takes degrees. The point that the degrees version is additionally
> exact on special values is an extra benefit.
No, that's not an extra benefit, it is the only benefit!
If we can't make it exact for the obvious degree angles, there would be
no point in doing this. We'd just tell people to write their own
two-line functions:
def sindeg(angle):
return math.sin(math.radians(angle))
The only reason to even consider making this a standard library function
is if we can do better than that.
> It would be nice to also fix the original sin,
The sin function is not broken and does not need fixing.
(Modulo quirks of individual platform maths libraries.)
> or more precisely to provide a way to give it a
> fractional multiple of pi. How about a special class PiMultiple that would
> represent a fractional multiple of pi?
What is the point of that? When you pass it to math.sin, it still needs
to be converted to a float before sin can operate on it.
Unless you are proposing a series of dunder methods __sin__ __cos__ and
__tan__ to allow arbitrary classes to be passed to sin, cos and tan, the
following cannot work:
> PI = PiMultiple(1)
> assert PI / 2 == PiMultiple(1, 2)
> assert cos(PI / 2) == 0
Without a __cos__ dunder method that allows PiMultiple objects to
customise the result of cos(), that last line has to fail, because
cos(math.pi/2) == 0 fails.
> DEG = 2 * PI / 360
> assert sin(45 * DEG) == sqrt(2) / 2
Likewise.
On 6/8/18 01:45, Robert Vanden Eynde wrote:
> - Thanks for pointing out a language (Julia) that already had a name convention.
> Interestingly they don't have a atan2d function. Choosing the same convention as
> another language is a big plus.
For what it's worth, scipy calls them sindg, cosdg, tandg, cotdg.
https://docs.scipy.org/doc/scipy-1.1.0/reference/special.html
Steven D'Arpano wrote:
> On Fri, Jun 08, 2018 at 11:11:09PM +0200, Adam Bartoš wrote: > >> But if there are both sin and dsin, and you ask about the difference >> between them, the obvious answer would be that one takes radians and the >> other takes degrees. The point that the degrees version is additionally >> exact on special values is an extra benefit. > > No, that's not an extra benefit, it is the only benefit! > > If we can't make it exact for the obvious degree angles, there would be > no point in doing this. We'd just tell people to write their own > two-line functions: > > def sindeg(angle): > return math.sin(math.radians(angle)) > > > The only reason to even consider making this a standard library function > is if we can do better than that.
I agree completely, I just think it doesn't look obvious.
>> It would be nice to also fix the original sin, > > The sin function is not broken and does not need fixing. > > (Modulo quirks of individual platform maths libraries.) > > >> or more precisely to provide a way to give it a >> fractional multiple of pi. How about a special class PiMultiple that would >> represent a fractional multiple of pi?
> > What is the point of that? When you pass it to math.sin, it still needs > to be converted to a float before sin can operate on it. > > Unless you are proposing a series of dunder methods __sin__ __cos__ and > __tan__ to allow arbitrary classes to be passed to sin, cos and tan, the > following cannot work.
The idea was that the functions could handle the PiMultiple instances in a special way and fall back to float only when a special value is not detected. It would be like the proposed dsin functionality, but with a magic class instead of a new set of functions, and without a particular choice of granularity (360 degrees).
But maybe it isn't worth it. Also what about acos(0)? Should it return PiMultiple(1, 2) and confuse people or just 1.5707963267948966 and loose exactness?
Best regards,
Adam Bartoš
The idea was that the functions could handle the PiMultiple instances in a special way and fall back to float only when a special value is not detected. It would be like the proposed dsin functionality, but with a magic class instead of a new set of functions, and without a particular choice of granularity (360 degrees).But maybe it isn't worth it. Also what about acos(0)? Should it return PiMultiple(1, 2) and confuse people or just 1.5707963267948966 and loose exactness?
For the naming convention, scipy using sindg (therefore Nor sind nor sindeg) will make the sind choice less obvious. However if Matlab and Julia chooses sind that's a good path to go, Matlab is pretty popular, as other pointed out, with Universities giving "free" licences and stuff. With that regards, scipy wanting to "be a replacement to Matlab in python and open source" it's interesting they chose sindg and not the Matlab name sind.
On 6/10/2018 10:44 AM, Stephan Houben wrote:
> I would suggest that compatibility with a major Python library such as
> SciPy is more important than compatibility
> with other programming languages.
>
> I would go even further and argue that scipy.special.sindg and its
> friends cosdg and tandg
> can serve as the reference implementation for this proposal.
Or we could decide that we don't need to duplicate scipy.
--
Terry Jan Reedy
_______________________________________________
Python-ideas mailing list
Python...@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/
--
---
You received this message because you are subscribed to a topic in the Google Groups "python-ideas" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/python-ideas/-NauPA0ZckE/unsubscribe.
To unsubscribe from this group and all its topics, send an email to python-ideas...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
What I meant is to not add functions that already exist in scipy. Core
devs do not need the burden of keeping up with whatever improvements are
made to the scipy functions.
I agree that a big python library is more close to the standard python lib than matlab. However, helping transition from matlab is a great concern in the python scientific community, because matlab is used is a lot of engineering classes at University.
That's a tough call hmmm.
> In regard to the "special values", and exact results -- a good math lib
> should return results that are "exact" in all but maybe the last digit
> stored. So you could check inputs and outputs with, e.g. math.isclose() to
> give people the "exact" results. -- and keep it all in floating point.
I wish Uncle Timmy or Mark Dickinson were around to give a definite
answer, but in their absence I'll have a go. I'm reasonably sure
that's wrong.
The problem with trig functions is that they suffer from "the
table maker's dilemma", so it is very hard to guarantee a correctly
rounded result without going to ludicrous extremes:
http://perso.ens-lyon.fr/jean-michel.muller/Intro-to-TMD.htm
So I think that there's no guarantee given for trancendental functions
like sine, cosine etc.
But even if they were, using isclose() is the wrong solution. Suppose
sin(x) returns some number y, such that isclose(y, 0.0) say. You have no
way of knowing that y is an inaccurate result that ought to be zero, or
whether the answer should be non-zero and y is correct. You cannot
assume that "y is close to zero, therefore it ought to be zero".
It's not just zero, the same applies for any value. That's just moving
rounding errors from one input to a slightly different input.
# current situation
sine of x returns y, but the mathematical exact result is exactly z
# suggested "fix"
sine of x ± a tiny bit returns exactly z, but ought to return y
Guessing what sin or cos "ought to" return based on either the inexact
input or inexact output is not a good approach.
Remember, because π is irrational, we cannot actually call sin or cos on
any rational multiple of π. We can only operate on multiples of pi,
which is *close to* but not the same as π. That's why it is okay that
tan(pi/2) returns a huge number instead of infinity or NAN. That's
because the input is every so slightly smaller than π/2. That's exactly
the behaviour you want when x is ever so slightly smaller than π/2.
This would basically be the reason for a PiMultiple class - you can
special case it. You'd know sin(PiMultiple(0.5)) == 0. You'd know
cos(PiMultiple(0.5)) == -1 and tan(PiMultiple(0.5)) == nan. This could
let you remember as much angles as possible into multiples of pi, and
as long as you're in multiples of pi, you're exact.
PiMultiple(Fraction(1, 6)) would be exact and could give the right
sin() and cos() behaviour.
And because it'd be a numeric type, you could still use it with all
other numeric types and add/multiply etc it. When you add and subtract
it with another numeric type, it'd lose the special status, but even
with multiples and divisions you can preserve it's specialness.
And if it gets weird values, you can always fall back on converting it
to a float, therefore never giving worse results.
It also gives a reason -against- degrees. if you have PiMultiple or
TauMultiple, it's rather easy to give common angles, and students can
learn to properly learn radians for angles as they should.(because,
lets be honest, they're objectively better measures of angles than
degrees, or even *shiver* grads. )
We SHOULD make it easy to code exact and the right way, and I think a
PiMultiple class could help that a lot. That said, it does need a
better name.
What is the real world advantage of such a class? So far I’ve only seen examples where the current behavior is said to be confusing for students. In most cases where I have used math.sin the angle wasn’t a constant and wasn’t an exact mulltiple of pi.
Ronald
Im assuming the current math.pi would be converted to PiMultiple(1)
When learning, it's rather easy to write and read the following:
>>> from math import sin, pi, asin, cos
>>> myangle = pi / 2
>>> sin(myangle)
1
>>> asin(1)
"0.5π" # Currently: 1.5707963267948966
>>> cos(pi / 2)
0 # Currently: 6.123233995736766e-17
It helps clarity and understanding when you're coming to python from a
math background. In universities, half of your values when working
with angles are written as some multiple of pi (of some weird fraction
involving it). Also, for the common angles, we'll gain accuracy (and
perhaps we can avoid the infinite series for certain computations.
That would be a win.).
Also, you're countering the "confusing to students" part with your own
production environment experience. Those aren't alike. And if this was
done, your float-based production code wouldn't get slower or care any
other way. What you implement and test with PiMultiple would work
perfectly fine with any random float as well, just without the extra
advantages.
Not when I went to school it wasn't. sin(π/2) = sin(90°) = 1.
Perhaps you meant cos?
In any case, the sin function doesn't work that way. Unless we add a
special dunder method to defer to, it cannot be expected to magically
know how to deal with these "PiMultiple" objects, except by converting
them to floats.
You don't suddenly get accurate results by waving a magic wand over the
float 0.5 and saying "You're a multiple of pi". You still have to code a
separate algorithm for this, and that's hard work. (Why do you think the
decimal module doesn't support trig functions?)
Is every function that takes float arguments now supposed to recognise
PiMultiple objects and treat them specially? How do they integrate in
the numeric tower and interact with ordinary floats?
But let's say you do this:
def sin(arg):
if isinstance(arg, PiMultiple):
# does this mean it needs to be a builtin class?
call internal sinpi(arg) function
else:
call regular sin(arg) function
This isn't Java, and not everything needs to be a class. If we go to the
trouble of writing separate sinpi() etc implementations, why hide one of
them behind a class (and not even in a proper object-oriented interface)
when we can just call the functions directly?
sinpi(1.5)
sin(PiMultiple(1.5))
I know which I'd rather use.
> It also gives a reason -against- degrees. if you have PiMultiple or
> TauMultiple, it's rather easy to give common angles,
What about the uncommon angles?
The whole point of this proposal is to make it easy to give angles in
degrees without the need to convert to radians, introducing rounding
errors over and above those introduced by the trig function itself.
> and students can
> learn to properly learn radians for angles as they should.(because,
> lets be honest, they're objectively better measures of angles than
> degrees, or even *shiver* grads. )
No they are not objectively better measures of angles. With radians, you
have to divide a right angle into an irrational number of radians, one
which cannot be expressed in a finite number of decimal places.
In a very real sense, it is impossible to measure exactly 1 radian.
I know that in practical terms, this makes no difference, we can get
close enough, but physical measurements are limited to rational numbers.
A measurement system based on irrational numbers, especially one as
difficult as π, is not objectively better.
Its not just because of tradition that nobody uses radians in civil
engineering, astronomy, architecture, etc. Radians shine when we're
doing pure maths and some branches of physics, but they're a PITA to use
in most practical circumstances.
E,g, the tip of your little finger at arms length is close enough to 1°
or 0.017 radian. Who wants to measure angles in multiples of 0.017?
https://www.timeanddate.com/astronomy/measuring-the-sky-by-hand.html
Using radians, these heuristics stink.
--
Steve
> >>> asin(1)
> "0.5π" # Currently: 1.5707963267948966
I think that if you expect the stdlib math library to change to symbolic
maths for trig functions, you are going to be extremely disappointed.
Aside from everything else, this is such a massive backward-
compatibility break that it probably wouldn't even have been allowed in
Python 3.0, let alone in 3.8.
> It helps clarity and understanding when you're coming to python from a
> math background.
What about the other 95% of Python programmers who don't have a maths
background and don't give two hoots about the mathematical elegance of
being able to differentiate sin(x) without a multiplicative factor?
--
Steve
What is the real world advantage of such a class? So far I’ve only seen examples where the current behavior is said to be confusing for students.
In [49]: math.sin(math.pi)
Out[49]: 1.2246467991473532e-16
In [25]: x = 0.0
In [26]: for i in range(10): x += 0.1
In [27]: x
Out[27]: 0.9999999999999999
In [28]: 1.0 - x
Out[28]: 1.1102230246251565e-16
In [46]: math.sin(math.pi)
Out[46]: 1.2246467991473532e-16
In [47]: round(math.sin(math.pi), 15)
Out[47]: 0.0
Whoops, it turns out Euler's formula does work! I expected imprecision, but at least one test matched.x = 42cos(x) + 1j * sin(x) == e ** (1j * x)
I suppose that's because it's radians.
On Mon, Jun 11, 2018, 10:24 AM Michael Selik <mi...@selik.org> wrote:Would sind and cosd make Euler's formula work correctly?sind(x) + i * sind(x) == math.e ** (i * x)I suspect that adding these functions is kind of like those cartoons where the boat is springing leaks and the character tried to plug them with their fingers. Floating point is a leaky abstraction.Perhaps you'd prefer an enhancement to the fractions module that provides real (not float) math?
No, using degrees makes Euler's identity *not* work correctly, unless
you add in a conversion factor from degrees to radians:
https://math.stackexchange.com/questions/1368049/eulers-identity-in-degrees
Euler's Identity works fine in radians:
py> from cmath import exp
py> exp(1j*math.pi)
(-1+1.2246063538223773e-16j)
which is close enough to -1 given the usual rounding issues with floats.
(Remember, math.pi is not π, but a number close to it. There is no way
to represent the irrational number π in less than an infinite amount of
memory without symbolic maths.)
[...]
> Perhaps you'd prefer an enhancement to the fractions module that provides
> real (not float) math?
I should think not. Niven's Theorem tells us that for rational angles
between 0° and 90° (that is, angles which can be represented as
fractions), there are only THREE for which sine (and cosine) are
themselves rational:
https://en.wikipedia.org/wiki/Niven's_theorem
Every value of sin(x) except for those three angles is an irrational
number, which means they cannot be represented exactly as fractions or
in a finite number of decimal places.
What that means is that if we tried to implement real (not float)
trigonometric functions on fractions, we'd need symbolic maths capable
of returning ever-more complicated expressions involving surds.
For example, the exact value of sin(7/2 °) involves a triple nested
square root:
1/2 sqrt(2 - sqrt(2 + sqrt(3)))
and that's one of the relatively pretty ones. sin(3°) is:
-1/2 (-1)^(29/60) ((-1)^(1/60) - 1) (1 + (-1)^(1/60))
http://www.wolframalpha.com/input/?i=exact+value+of+sine%2815%2F2+degrees%29
http://www.wolframalpha.com/input/?i=exact+value+of+sine%283+degrees%29
This proposal was supposed to *simplify* the trig functions for
non-mathematicians, not make them mind-bogglingly complicated.
--
Steve
In fact, 1.0*x == x is almost all that this test exercises. If I'm looking in the right place, this is C the implementation of a ** b, omitting in a few special cases:
vabs = hypot(a.real,a.imag);
len = pow(vabs,b.real);
at = atan2(a.imag, a.real);
phase = at*b.real;
if (b.imag != 0.0) {
len /= exp(at*b.imag);
phase += b.imag*log(vabs);
}
r.real = len*cos(phase);
r.imag = len*sin(phase);
This means that (e ** ...) is essentially implemented in terms of the formula above. Indeed, in the special case of e ** (1j * x), we have a.real = e, a.imag = 0.0, b.real = 0.0, and b.imag = 1.0, so concretely the code simplifies to this:
vabs = e
len = 1.0
at = 0.0
phase = 0.0
if (b.imag != 0.0) {
len = 1.0;
phase = x; // requires log(e) == 1.0 and x * 1.0 == x
}
r.real = cos(phase); // requires 1.0 * x == x
r.imag = sin(phase);
Thus, it shouldn't be too surprising that the formula holds :)
Clément.
Would sind and cosd make Euler's formula work correctly?
Perhaps you'd prefer an enhancement to the fractions module that provides real (not float) math?
On Mon, Jun 11, 2018 at 10:24 AM, Michael Selik <mi...@selik.org> wrote:Would sind and cosd make Euler's formula work correctly?
There is nothing inherently more accurate in using degrees rather than radians for trigonometry.
Perhaps you'd prefer an enhancement to the fractions module that provides real (not float) math?Isn't that exactly what the fractions module does? or are you suggesting that it be extended with trig functions?
> In regard to the "special values", and exact results -- a good math lib
> should return results that are "exact" in all but maybe the last digit
> stored. So you could check inputs and outputs with, e.g. math.isclose() to
> give people the "exact" results. -- and keep it all in floating point.
I wish Uncle Timmy or Mark Dickinson were around to give a definite
answer, but in their absence I'll have a go. I'm reasonably sure
that's wrong.
The problem with trig functions is that they suffer from "the
table maker's dilemma", so it is very hard to guarantee a correctly
rounded result without going to ludicrous extremes:
http://perso.ens-lyon.fr/jean-michel.muller/Intro-to-TMD.htm
So I think that there's no guarantee given for trancendental functions
like sine, cosine etc.
But even if they were, using isclose() is the wrong solution. Suppose
sin(x) returns some number y, such that isclose(y, 0.0) say. You have no
way of knowing that y is an inaccurate result that ought to be zero, or
whether the answer should be non-zero and y is correct. You cannot
assume that "y is close to zero, therefore it ought to be zero".
It's not just zero, the same applies for any value. That's just moving
rounding errors from one input to a slightly different input.
# current situation
sine of x returns y, but the mathematical exact result is exactly z
# suggested "fix"
sine of x ± a tiny bit returns exactly z, but ought to return y
Guessing what sin or cos "ought to" return based on either the inexact
input or inexact output is not a good approach.
We can only operate on multiples of pi,
which is *close to* but not the same as π. That's why it is okay that
tan(pi/2) returns a huge number instead of infinity or NAN. That's
because the input is every so slightly smaller than π/2. That's exactly
the behavior you want when x is ever so slightly smaller than π/2.
In [10]: math.tan(math.pi / 2.0)
Out[10]: 1.633123935319537e+16
In [11]: math.tan(math.pi / 2.0 + 2e-16)
Out[11]: -6218431163823738.0
In [119]: def pretty_tan(x):
...: tol = 3e-16
...: diff = x % (pi / 2)
...: if abs(diff) < tol:
...: return float("-Inf")
...: elif (pi /2) - diff < tol:
...: return float("Inf")
...: return math.tan(x)
...:
...:
In [120]: x = pi / 2 - 5e-16
In [121]: for i in range(10):
...: val = x + i * 1e-16
...: print val, pretty_tan(val)
...:
1.57079632679 1.9789379661e+15
1.57079632679 1.9789379661e+15
1.57079632679 inf
1.57079632679 inf
1.57079632679 -inf
1.57079632679 -inf
1.57079632679 -inf
1.57079632679 -inf
1.57079632679 -2.61194216074e+15
1.57079632679 -2.61194216074e+15
--
Steve
_______________________________________________
Python-ideas mailing list
Python...@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/
Precisely. They are the values I'm talking about.
If you're serious about only supporting only the rational values, then
the implementation is trivial and we can support both degrees and
radians easily.
def sin(angle): # radians
if angle == 0:
return 0
raise ValueError("sorry, no rational sine for that angle")
def sind(angle): # degrees
angle %= 360
rational_values = {
0: 0, 30: 0.5, 90: 1, 150: 0.5,
180: 0, 210: -0.5, 270: -1, 330: -0.5
}
if angle in rational_values:
return rational_values[angle]
raise ValueError("sorry, no rational sine for that angle")
Exceedingly simple, and exceedingly useless. Which was my point:
supporting only the rational values is pointless, because there are only
a handful of them. We either have to support full-blown symbolic
results, or we have rational APPROXIMATIONS to the true value.
I'm responding to a proposal that explicitly suggested using fractions
to do "real (not float) math", which I read as "no approximations". I
imagine that Michael thought that by using fractions, we can calculate
exact rational results for sine etc without floating point rounding
errors. No we cannot, except for the values above.
If "not float" is serious, then with the exception of the above values,
*none* of the values will be rational and we either can't return a value
(except for the above) or we have to use symbolic maths.
Using fractions is not a magic panacea that lets you calculate exact
answers just by swapping floats to fractions.
> I wonder whether another solution would be to provide a
> set of "newbie math" functions that round their results.
[...]
> Yes, I know, this just pushes the surprises somewhere
> else, but so does every solution.
No, that's really not true of every solution.
The initial proposal is fine: a separate set of trig functions that take
their arguments in degrees would have no unexpected surprises (only the
expected ones). With a decent implementation i.e. not this one:
# don't do this
def sind(angle):
return math.sin(math.radians(angle))
and equivalent for cos, we ought to be able to get correctly rounded
values for nearly all the "interesting" angles of the circle, without
those pesky rounding issues caused by π not being exactly representable
as a float.
There will still be rounding errors, because we're dealing with numbers
like sqrt(2) etc, but with IEEE-754 maths, they'll be correctly
rounded.
--
Steve
Actually there is: using radians, the only "nice" angle that can be
represented exactly is 0. With degrees, we can represent a whole lot of
"nice" angles exactly. "Nice", is of course subjective, but most of us
would recognise that 36° represented as exactly 36.0 is nice but being
*approximately* represented as 0.6283185307179586 is not.
Using radians, we have two sources of rounding error:
- π cannot be represented exactly as a float, so we have to use a
number pi which is ever-so-slightly off;
- plus the usual round-off error in the algorithm;
while using degrees, we only have the second one (since 180° *can* be
represented exactly, as the float 180.0).
And with the degrees implementation, we should be able to use correctly
rounded roots for many of our "nice" angles.
> And every computer math lib I've even seen uses floating point radians for
> trig functions, so unless you're really going to implement trig from
> degrees from scratch
Well that's the whole point of the discussion.
> Oh, and radians are the more "natural" units (in fact unitless) for math,
Degrees are unit-less too. 180° = π radians. That's just a scaling
factor difference.
Unless you're doing symbolic maths, differentiating or integrating trig
functions, or certain geometric formulae which are "neater" in radians
than in degrees, there's no real advantage to radians.
Degrees are simply a much more practical unit of angle for practical
work.
> and the only way that things like the Euler identity work.
Its not the *only* way.
https://math.stackexchange.com/questions/1368049/eulers-identity-in-degrees
> Which is why computational math libs use them.
Actually, more maths libraries than you might guess offer trig functions
in degrees, or scaled by pi, e.g:
https://docs.oracle.com/cd/E19957-01/806-3568/ncg_lib.html
Julia and Matlab provide sind etc, and although I cannot find a
reference right now, I seem to recall the latest revision to the IEEE
754 standard suggesting them as optional functions.
--
Steve
...
The initial proposal is fine: a separate set of trig functions that take
their arguments in degrees would have no unexpected surprises (only the
expected ones). With a decent implementation i.e. not this one:
# don't do this
def sind(angle):
return math.sin(math.radians(angle))
and equivalent for cos, we ought to be able to get correctly rounded
values for nearly all the "interesting" angles of the circle, without
those pesky rounding issues caused by π not being exactly representable
as a float.
Number representation::
IEEE-754 doubles cannot represent pi correctly at any bit-depth (i
mean, obviously, but more seriously).
input pi = 3.14159265358979323846264338327
output pi = 3.14159265358979311599796346854
The cutoff is the value everyone uses, which is what is in math.pi
among other places.
3.141592653589793
Any conversion using this value of pi is already wrong, and the input
to the function is already wrong. The function doesn't matter. It's
always underestimating if using pi. I'm clarifying this because it's
separate from any discussion of functions. More accurate or different
functions cannot use a better input pi.
Calculator: http://www.binaryconvert.com/convert_double.html
input pi source: https://www.piday.org/million/
Libraries:
Boost implements a cos_pi function, it's algorithm will probably be
useful to look at.
glibc implements cos/sin as a look-up table, which is most likely
where any other implementation will end up, as it is a common
endpoint. I've seen this on different architectures and libraries.
(sincostab.h if you want to google glibc's table). Maybe there is a
hilarious index lookup off-by-1 there, I didn't look.
Tables are the basis for the calculation in many libraries in march
architectures, so I wanted to point that out to any function
designers. A new implementation may come down to calculating right
index locations.
For a degree based implementation, the same algorithm can be used with
a different table input that is calibrated to degrees.
Likewise, a pi based implementation, the same but for the pi scale
factor input.
Nothing needs to be done designed other than a new lookup table
calibrated for the "base" of a degree, radian, or pi.. it will have
the same output precision issues calculating index values, but at
least the input will be cleaner.
This is not me saying what should be done, just giving information
that may hopefully be useful
Small note about python's math:
The python math library does not implement algorithms, it exposes the
c functions. You can see C/C++ has this exact issue performing the
calculation there. As that is "the spec," the spec is defined with
the error. The math library is technically correct given it's stated
purpose and result.
Technically correct, the best kind of correct.
https://www.youtube.com/watch?v=hou0lU8WMgo
Hi all,I wrote a possible implementation of sindg:This code first reduces the angle to the [0,90] interval.After doing so, it can be observed that the simple implementationmath.sin(math.radians(angle))produces exact results for 0 and 90, and a result already rounded to nearest for60.
For 30 and 45, this simple implementation is one ulp too low.So I special-case those to return the correct/correctly-rounded value instead.Note that this does not affect monotonicity around those values.
# Python, NumPy, SymPy, mpmath, sage trigonometric functions## Python math module- degrees(radians): Float degrees- radians(degrees): Float degrees## NumPy- degrees(radians) : List[float] degrees- rad2deg(radians): List[float] degrees- radians(degrees) : List[float] radians- deg2rad(degrees): List[float] radians
## SymPyhttp://docs.sympy.org/latest/modules/functions/elementary.html#trionometric-functions- sympy.mpmath.degrees(radians): Float degrees- sympy.mpmath.radians(degrees): Float radians- cosd, sind
> Let x, theta, phi, etc. be Symbols representing quantities in radians. Keep a list of these symbols: angles = [x, theta, phi]. Then, at the very end, use y.subs([(angle, angle*pi/180) for angle in angles]) to change the meaning of the symbols to degrees"
# I. Trigonometry
@pytest.mark.xfail
def test_I1():
assert tan(7*pi/10) == -sqrt(1 + 2/sqrt(5))
@pytest.mark.xfail
def test_I2():
assert sqrt((1 + cos(6))/2) == -cos(3)
def test_I3():
assert cos(n*pi) + sin((4*n - 1)*pi/2) == (-1)**n - 1
def test_I4():
assert cos(pi*cos(n*pi)) + sin(pi/2*cos(n*pi)) == (-1)**n - 1
@pytest.mark.xfail
def test_I5():
assert sin((n**5/5 + n**4/2 + n**3/3 - n/30) * pi) == 0
## mpmath- sympy.mpmath.degrees(radians): Float degrees- sympy.mpmath.radians(degrees): Float radians## Sage
On Friday, June 8, 2018, Robert Vanden Eynde <robertva...@hotmail.com> wrote:- Thanks for pointing out a language (Julia) that already had a name convention. Interestingly they don't have a atan2d function. Choosing the same convention as another language is a big plus.
- Adding trig function using floats between 0 and 1 is nice, currently one needs to do sin(tau * t) which is not so bad (from math import tau, tau sounds like turn).
- Julia has sinpi for sin(pi*x), one could have sintau(x) for sin(tau*x) or sinturn(x).
Grads are in the idea of turns but with more problems, as you guys said, grads are used by noone, but turns are more useful. sin(tau * t) For The Win.
- Even though people mentionned 1/6 not being exact, so that advantage over radians isn't that obvious ?
from math import sin, taufrom fractions import Fractionsin(Fraction(1,6) * tau)sindeg(Fraction(1,6) * 360)
These already work today by the way.
- As you guys pointed out, using radians implies knowing a little bit about floating point arithmetic and its limitations. Integer are more simple and less error prone. Of course it's useful to know about floats but in many case it's not necessary to learn about it right away, young students just want their player in the game move in a straight line when angle = 90.
- sin(pi/2) == 1 but cos(pi/2) != 0 and sin(3*pi/2) != 1 so sin(pi/2) is kind of an exception.
Le ven. 8 juin 2018 à 09:11, Steven D'Aprano <st...@pearwood.info> a écrit :
On Fri, Jun 08, 2018 at 03:55:34PM +1000, Chris Angelico wrote:
> On Fri, Jun 8, 2018 at 3:45 PM, Steven D'Aprano <st...@pearwood.info> wrote:
> > Although personally I prefer the look of d as a prefix:
> >
> > dsin, dcos, dtan
> >
> > That's more obviously pronounced "d(egrees) sin" etc rather than "sined"
> > "tanned" etc.
>
> Having it as a suffix does have one advantage. The math module would
> need a hyperbolic sine function which accepts an argument in; and
> then, like Charles Napier [1], Python would finally be able to say "I
> have sindh".
Ha ha, nice pun, but no, the hyperbolic trig functions never take
arguments in degrees. Or radians for that matter. They are "hyperbolic
angles", which some electrical engineering text books refer to as
"hyperbolic radians", but all the maths text books I've seen don't call
them anything other than a real number. (Or sometimes a complex number.)
But for what it's worth, there is a correspondence of a sort between the
hyperbolic angle and circular angles. The circular angle going between 0
to 45° corresponds to the hyperbolic angle going from 0 to infinity.
https://en.wikipedia.org/wiki/Hyperbolic_angle
https://en.wikipedia.org/wiki/Hyperbolic_function
> [1] Apocryphally, alas.
Don't ruin a good story with facts ;-)
--
Steve
[Tim]
>> 1. Python's float "%" is unsuitable for argument reduction; e.g.,
>>
>> >>> -1e-14 % 360.0
>> 360.0
>>
>> `math.fmod` is suitable, because it's exact:
>>
>> >>> math.fmod(-1e-14, 360.0)
>> -1e-14
[Greg Ewing]
> So why doesn't float % use math.fmod?
https://docs.python.org/3/reference/expressions.html#binary-arithmetic-operations
https://docs.python.org/3/reference/expressions.html#id17
https://docs.python.org/3/reference/expressions.html#id18
(the latter two being footnotes from the section in the first link)
With real numbers, divmod (and thus the // and % operators) would
always return values such that:
div, mod = divmod(x, y):
1) div*y + mod == x
2) sign(mod) == sign(y)
3) 0 <= abs(mod) < abs(y)
But with floats, you can't guarantee all three of these. The divmod
function focuses on the first, guaranteeing the fundamental arithmetic
equality, but to do so, it sometimes has to bend the third one and
return mod==y.
On Tue, Jun 12, 2018, 00:03 Stephan Houben <steph...@gmail.com> wrote:Hi all,I wrote a possible implementation of sindg:This code first reduces the angle to the [0,90] interval.After doing so, it can be observed that the simple implementationmath.sin(math.radians(angle))produces exact results for 0 and 90, and a result already rounded to nearest for60.You observed this on your system, but math.sin uses the platform libm, which might do different things on other people's systems.
_______________________________________________
What was wrong with my initial implementation with a lookup table ? :Ddef sind(x):if x % 90 == 0:return (0, 1, 0, -1)[int(x // 90) % 4]else:return sin(radians(x))
If you want to support multiples of 30, you can do % 30 and // 30.
Then of you also want 45, you could do % 15 ? :D
As I stated in my initial comment on this, if you are going to create a
sind function with the idea that you want 'nice' angles to return
'exact' results, then what you need to do is have the degree based trig
routines do the angle reduction in degrees, and only when you have a
small enough angle, either use the radians version on the small angle or
directly include an expansion in degrees.
Angle reduction would be based on the identity that sin(x+y) = sin(x) *
cos(y) + cos(x) * sin(y) and cos(x+y) = cos(x)*cos(y) - sin(x) * sin(y).
If you want to find sin(z) for an arbitrary value z, you can reduce it
to and x+y where x is some multiple of say 15 degrees, and y is in the
range -7.5 to 7.5 degrees. You can have stored exact values of sin/cos
of the 15 degree increments (and only really need them between 0 and 90)
and then compute the sin and cos of the y value.
On 6/13/18 6:07 AM, Stephan Houben wrote:
> 2018-06-13 12:00 GMT+02:00 Robert Vanden Eynde <rober...@gmail.com
> <mailto:rober...@gmail.com>>:
>
> What was wrong with my initial implementation with a lookup table
> ? :D
>
> def sind(x):
> if x % 90 == 0:
> return (0, 1, 0, -1)[int(x // 90) % 4]
> else:
> return sin(radians(x))
>
>
> I kinda missed it, but now you ask:
>
> 1. It's better to reduce the angle while still in degrees since one of
> the advantages
> of degrees is that the reduction can be done exactly. Converting
> very large angles
> first to radians and then taking the sine can introduce a large error,
>
> 2. I used fmod instead of % on advice in this thread.
>
> 3. I also wanted to special case, 30, 45, and 60.
>
>
>
> If you want to support multiples of 30, you can do % 30 and // 30.
>
>
> Sure, but I also wanted to special-case 45.
>
> Stephan
>
>
>
> Le mer. 13 juin 2018 à 09:51, Stephan Houben <steph...@gmail.com
> <mailto:steph...@gmail.com>> a écrit :
>
> Op di 12 jun. 2018 12:41 schreef Nathaniel Smith
> <n...@pobox.com <mailto:n...@pobox.com>>:
> Python...@python.org <mailto:Python...@python.org>
> https://mail.python.org/mailman/listinfo/python-ideas
> <https://mail.python.org/mailman/listinfo/python-ideas>
> Code of Conduct: http://python.org/psf/codeofconduct/
> <http://python.org/psf/codeofconduct/>
>
>
> _______________________________________________
> Python-ideas mailing list
> Python...@python.org <mailto:Python...@python.org>
> https://mail.python.org/mailman/listinfo/python-ideas
> <https://mail.python.org/mailman/listinfo/python-ideas>
> Code of Conduct: http://python.org/psf/codeofconduct/
> <http://python.org/psf/codeofconduct/>
>
>
>
>
> _______________________________________________
> Python-ideas mailing list
> Python...@python.org
> https://mail.python.org/mailman/listinfo/python-ideas
> Code of Conduct: http://python.org/psf/codeofconduct/
--
Richard Damon
My first comment is that special casing values like this can lead to
some very undesirable properties when you use the function for numerical
analysis. Suddenly your sind is no longer continuous (sind(x) is no
longer the limit of sind(x+d) as d goes to 0).
As I stated in my initial comment on this, if you are going to create a
sind function with the idea that you want 'nice' angles to return
'exact' results, then what you need to do is have the degree based trig
routines do the angle reduction in degrees, and only when you have a
small enough angle, either use the radians version on the small angle or
directly include an expansion in degrees.
Angle reduction would be based on the identity that sin(x+y) = sin(x) *
cos(y) + cos(x) * sin(y) and cos(x+y) = cos(x)*cos(y) - sin(x) * sin(y).
If you want to find sin(z) for an arbitrary value z, you can reduce it
to and x+y where x is some multiple of say 15 degrees, and y is in the
range -7.5 to 7.5 degrees. You can have stored exact values of sin/cos
of the 15 degree increments (and only really need them between 0 and 90)
and then compute the sin and cos of the y value.
My first comment is that special casing values like this can lead to
some very undesirable properties when you use the function for numerical
analysis. Suddenly your sind is no longer continuous (sind(x) is no
longer the limit of sind(x+d) as d goes to 0).
As I stated in my initial comment on this, if you are going to create a
sind function with the idea that you want 'nice' angles to return
'exact' results, then what you need to do is have the degree based trig
routines do the angle reduction in degrees, and only when you have a
small enough angle, either use the radians version on the small angle or
directly include an expansion in degrees.
...
import mpmathfrom math import fmod# Return (n, x) such that:# 1. d degrees is equivalent to x + n*(pi/2) radians.# 2. x is an mpmath float in [-pi/4, pi/4].# 3. n is an integer in range(4).# There is one potential rounding error, when mpmath.radians() is# used to convert a number of degrees between -45 and 45. This is# done using the current mpmath precision.def treduce(d):d = fmod(d, 360.0)n = round(d / 90.0)assert -4 <= n <= 4d -= n * 90.0assert -45.0 <= d <= 45.0return n & 3, mpmath.radians(d)
One property that we like to preserve in functional calculation is that
the following pseudo code
dp = x + delta
derivative = ( f(xp) - f(x) ) / (xp - x)
(or variations where you subtract delta or work at x+delta and x-delta)
should approximate well the derivative of the function, (which for sin
in radians should be cos), and that this improves as delta gets very
small until we hit the rounding error in the computation of f(x). (Note,
I don't divide by delta, but xp-x to remove the round off error in
computing xp which isn't the fault of the function f). Changing a point
because it is the closest to the nice number will cause this calculation
to spike due to the single point perturbation). Yes, this calculation
may start to 'blow up' f(xp) - f(x) is very small compared to f(x) and
we start to measure the round off error in the computation of the
function, near a zero of the function, (which if we are root finding is
common) we can do quite well.
And that is what my method did (as others have said). Virtual all
methods of computing sin and cos use the angle addition formula (and
even quadrant reduction is using it for the special case of using one of
the angles where sin/cos are valued in-1, 0, 1). The methods that least
use it that I know of reduces the angle to a quadrant (or octant) and
then selects one a number of expansions good for a limited range, or
table interpolation (but even that sort of uses it with the
approximation of sin(x) ~ x and cos(x) ~ 1 for very small x)
Reading your digressions on the minutia of floating point maths is
certainly an education. It makes algebra and real-valued mathematics
seem easy in comparison.
I still haven't got over Mark Dickinson's demonstration a few years
back that under Decimal floating point, but not binary, it is possible
for the ordinary arithmetic average (x+y)/2 to be outside of the
range [x, y]:
py> from decimal import getcontext, Decimal
py> getcontext().prec = 3
py> x = Decimal('0.516')
py> y = Decimal('0.518')
py> (x + y) / 2
Decimal('0.515')
Ya, decimal fp doesn't really solve anything except the shallow surprise that decimal fractions generally aren't exactly representable as binary fractions. Which is worth a whole lot for casual users, but doesn't address any of the deep problems (to the contrary, it makes those a bit worse).
On Sat, Jun 16, 2018 at 10:57 PM, Tim Peters <tim.p...@gmail.com> wrote:Ya, decimal fp doesn't really solve anything except the shallow surprise that decimal fractions generally aren't exactly representable as binary fractions. Which is worth a whole lot for casual users, but doesn't address any of the deep problems (to the contrary, it makes those a bit worse).It's my suspicion that the story is the same with "degree-based" trig :-)Which is why, if you want "nice-looking" results, it seems one could simply accept a decimal digit or so less precision, and use the "regular" FP trig functions, rounding to 14 or so decimal digits.Though if someone really wants to implement trig in native degrees -- more power to 'em.However -- if this is really such a good idea -- wouldn't someone have make a C lib that does it? Or has someone? Anyone looked?
-CHB--
Christopher Barker, Ph.D.
Oceanographer
Emergency Response Division
NOAA/NOS/OR&R (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception
Chris....@noaa.gov
Certainly! scipy.special uses the functions implemented in the Cephes C library.
--
Robert Kern
"I have come to believe that the whole world is an enigma, a harmless enigma
that is made terrible by our own mad attempt to interpret it as though it had
an underlying truth."
-- Umberto Eco
Maybe a library of trig functions that include an Angle type? This is related to the timespan units discussion we just had
def sin(angle):
On Thu, Jun 7, 2018 at 10:38 PM Stephen J. Turnbull <turnbull....@u.tsukuba.ac.jp> wrote:
6.123233995736766e-17
>>>
is good enough for government work, including at the local public high
school.
There probably is room for a library like "fractions" that represents multiples of pi or degrees precisely. I'm not sure how complicated or valuable of an endeavor that would be. But while I agree that floating point is good enough, we probably can do better.