You get better results, less numerical errors, if you start with the smallest summand and then use a Horner schema. But the problem is, how to figure out which index k
should give you the smallest summand ak
?
For sin/1
the summand is an = (-1)n * x(2*n+1) / (2n+1)!
.
For x = u * 10(-v)
, where u
is the mantissa of x
and v
is the negated exponent of x
, we see that:
|an| < |x(2*n+1)| = 10((log10(|u|) - v) * (2*n+1))
So if we want p
digits, we could try as smallest summand the index
k = ceiling(p / (2 * (log10(|u|) - v)))
This works best if we already have |x| =< 1/2
as in the question. The smallest summand index k
is computed here:
mp_sin(X, P, Y) :-
K is integer(ceiling(requested(P)/(2*dec_log10(X)))),
init_sin(K, X, P, U),
mp_sin(U, X, P, Y).
The predicate init_sin/4
and mp_sin/4
then compute sine backwards from the smallest summand to the biggest summand a0
, using Horner schema to get the sum as well at the same time:
mp_sin((0, S), _, _, S) :- !.
mp_sin(U, X, P, Y) :-
next_sin(U, X, P, V),
mp_sin(V, X, P, Y).
init_sin(K, X, P, (K, S)) :-
(K mod 2 =:= 0 -> V = X; V = -X),
mp_math(V/(2*K+1), P, S).
next_sin((L, T), X, P, (K, S)) :-
K is L-1,
(K mod 2 =:= 0 -> V = X; V = -X),
mp_math((T*X*X/(2*K+2)+V)/(2*K+1), P, S).
The internal predicate mp_math/3
is here used for BigDecimal arithmetic, so that we can also compute sin/1
with a precision of p = 100
, which is not possible with usual floats. Here are some results:
Jekejeke Prolog 4, Runtime Library 1.4.1 (20 August 2019)
?- use_module(library(decimal/multi)).
% 7 consults and 0 unloads in 222 ms.
Yes
?- X is mp(15*(pi/180),5), R is mp(sin(X),5).
X = 0d0.26181,
R = 0d0.25883
?- X is mp(15*(pi/180),102), R is mp(sin(X),102).
X = 0d0.2617993877991494365385536152
732919070164307832812588184145787160
256513671905174165523362354451764223
32,
R = 0d0.2588190451025207623488988376
240483283490689013199305138140032073
150569747488019969223679746942496655
21
Since all computations are done in the given precision, when you compute backwards with Horner schema, you need very few bits. Less numerical errors means you can drive the computation with less bits. But you need a little extra precision nevertheless.
To get a p = 4
result I was using p = 5
, and to get a p = 100
result I was using p = 102
. Did not yet have time to find a heuristic for this extra precision, and make it transparent for the end-user. Also often we could use a smaller k
, so this is still work in progress.