Просто дополнение
Мне пришлось подумать о том, действительно ли программа суммирует маленькие поплавки в последовательно большие пополам, а не маленькие поплавки в большие пополам (что может сделать результат более неточным, чем нужно), но это так.
Хотя нелегко полностью пересчитать факториал для каждого элемента ряда Тейлора и не использовать -1 * (k mod 2)
для непосредственного получения (-1)^k
, вместо этого проходя рекурсию.
Вот диаграмма вызова для ориентации:
Приложение 2: Код для более эффективного вычисления
Итак Я потратил некоторое время, чтобы выполнить упражнение по написанию cos
приближения, которое просто повторяется само по себе и несет всю вспомогательную информацию для вычисления терминов и и суммы.
% ===
% Entry point!
% Evaluate the Taylor series for cos(z) at "z" (not too far from 0, probably
% less than 1). The terms (sum elements) for index values 0..K are computed
5 and added. (K >= 0)
% ===
taylor_cos(Res,Z,Kmax,Verbose) :-
Zf is Z*1.0, % make a float
float(Zf),
integer(Kmax),Kmax >= 0,
Zsq is Zf*Zf,
at_element_k(Res,0,Kmax,Zsq,_,_,Verbose).
% The value computed is always the first one
even(K) :- integer(K), (K mod 2) =:= 0. % eval left & compare numerically
odd(K) :- integer(K), (K mod 2) =:= 1. % eval left & compare numerically
% Compute (-1)^k, k an integer >= 0.
% Computed value is on first place in predicate argument list.
minus_one_tothe_k( 1,K) :- even(K),!. % ! to make this deterministic
minus_one_tothe_k(-1,K) :- odd(K). % actually no need to test odd(K)
% Compute (2*k)!, k an integer >= 0, if (2*(k-1))! is known.
% Computed value is on first place in predicate argument list.
% The base case is conceptually jarring as the "prior value" can be anything.
% This is not unlike a function becoming evaluatable because of lazy evaluation.
two_times_k_factorial(1 ,0,_) :- !.
two_times_k_factorial(Res,K,ResPrior) :- K>0, Res is ResPrior*K*(4*K-2).
% Compute (z^(2*k)), k an integer >= 0, if (z^(2*(k-1))) is known.
% z² is passed too so that we do not need to recompute it again and again.
% Computed value is on first place in predicate argument list.
z_tothe_2k(1, 0, _ ,_) :- !.
z_tothe_2k(Res, K, Zsq ,ResPrior) :- K>0, Res is ResPrior * Zsq.
% Compute the Taylor series by summing the elements(k) with k in [0..Kmax)
% (so Kmax >= 1).
% When calling this initially, the values for TTKFprior and ZTT2Kprior
% are of no importance.
% The procedures calls itself recursively to compute element(i), element(i+1)
% etc. based on prior intermediate values. The base case is attained when
% K > Kmax. The sum accumulates in SumFromKmaxBackwards when the recursion
% comes back up the stack.
at_element_k(0.0,K,Kmax,_,_,_,Verbose) :-
K > Kmax,!,
((Verbose = verbose) ->
format("past the end as K=~d > Kmax=~d, returning back up the stack\n",[K,Kmax]) ; true).
at_element_k(SumFromKmaxBackwards,K,Kmax,Zsq,TTKFprior,ZTT2Kprior,Verbose) :-
minus_one_tothe_k(M1TTK,K), % M1TTK = (-1)^K
two_times_k_factorial(TTKF,K,TTKFprior), % TTKF = f(K,TTKFprior)
z_tothe_2k(ZTT2K,K,Zsq,ZTT2Kprior), % ZTT2K = f(K,z²,ZTT2Kprior)
ElementK is M1TTK * ZTT2K / TTKF, % element_k = M1TTK * (ZTT2K / TTKF)
((Verbose = verbose) -> format("element(~d) = ~e\n",[K,ElementK]) ; true),
KP1 is K+1,
at_element_k(SumFromKmaxBackwardsPrior,KP1,Kmax,Zsq,TTKF,ZTT2K,Verbose),
SumFromKmaxBackwards is SumFromKmaxBackwardsPrior + ElementK,
((Verbose = verbose) -> format("taylor-series-sum(~d ... ~d) = ~e (added ~e to prior value ~e)\n",
[K,Kmax,SumFromKmaxBackwards, ElementK, SumFromKmaxBackwardsPrior]) ; true).
Запустите это! Переменная Verbose
установлена в verbose
, чтобы генерировать больше распечаток во время вычисления ряда Тейлора. Мы вычисляем 11 членов серии (индексы 0 ... 10).
?- taylor_cos(Res,0.01,10,verbose).
element(0) = 1.000000e+00
element(1) = -5.000000e-05
element(2) = 4.166667e-10
element(3) = -1.388889e-15
element(4) = 2.480159e-21
element(5) = -2.755732e-27
element(6) = 2.087676e-33
element(7) = -1.147075e-39
element(8) = 4.779477e-46
element(9) = -1.561921e-52
element(10) = 4.110318e-59
past the end as K=11 > Kmax=10, returning back up the stack
taylor-series-sum(10 ... 10) = 4.110318e-59 (added 4.110318e-59 to prior value 0.000000e+00)
taylor-series-sum(9 ... 10) = -1.561920e-52 (added -1.561921e-52 to prior value 4.110318e-59)
taylor-series-sum(8 ... 10) = 4.779476e-46 (added 4.779477e-46 to prior value -1.561920e-52)
taylor-series-sum(7 ... 10) = -1.147074e-39 (added -1.147075e-39 to prior value 4.779476e-46)
taylor-series-sum(6 ... 10) = 2.087675e-33 (added 2.087676e-33 to prior value -1.147074e-39)
taylor-series-sum(5 ... 10) = -2.755730e-27 (added -2.755732e-27 to prior value 2.087675e-33)
taylor-series-sum(4 ... 10) = 2.480156e-21 (added 2.480159e-21 to prior value -2.755730e-27)
taylor-series-sum(3 ... 10) = -1.388886e-15 (added -1.388889e-15 to prior value 2.480156e-21)
taylor-series-sum(2 ... 10) = 4.166653e-10 (added 4.166667e-10 to prior value -1.388886e-15)
taylor-series-sum(1 ... 10) = -4.999958e-05 (added -5.000000e-05 to prior value 4.166653e-10)
taylor-series-sum(0 ... 10) = 9.999500e-01 (added 1.000000e+00 to prior value -4.999958e-05)
Res = 0.9999500004166653.
Ум 80-колонной *1029* Stackoverflow действует мне на нервы. В настоящее время у нас на экранах миллионы пикселей ширины, и они не используются и остаются белыми, потому что "Muh Visual Design" !! В любом случае ...
Теперь добавьте код для генерации Count
тестовых значений, равномерно распределенных между From
и To
. generator/4
генерирует последовательные значения при возврате. cos_compare/3
сравнивает то, что вычисляет наша cos
-аппроксимирующая функция и что система вычисляет (что происходит где-то в глубине в библиотеке ).
generator(X,From,To,1) :-
From =< To,
From_f is From*1.0,
To_f is To*1.0,
X is (From_f + To_f) / 2.0.
generator(X,From,To,Count) :-
integer(Count),
Count > 1,
From =< To,
From_f is From*1.0,
To_f is To*1.0,
Delta_f is (To_f - From_f)/(Count * 1.0),
CountM1 is Count-1,
between(0,CountM1,I),
X is From_f + Delta_f*I.
cos_compare(Z,Kmax,Verbose) :-
taylor_cos(Res,Z,Kmax,Verbose),
Cos is cos(Z),
Delta is abs(Res-Cos),
format("For z = ~e, k_max = ~d, difference to real cos = ~e\n", [Z, Kmax, Delta]).
Тогда давайте на самом деле сравним 100 значений между float -4.0
и float +4.0
и, где мы вычисляем 11 членов (индексы 0..11) ряда Тейлора для каждого значения:
run(Verbose) :- forall(generator(Z,-4.0,+4.0,100), cos_compare(Z,10,Verbose)).
?- run(quiet).
For z = -4.000000e+00, k_max = 10, difference to real cos = 1.520867e-08
For z = -3.920000e+00, k_max = 10, difference to real cos = 9.762336e-09
For z = -3.840000e+00, k_max = 10, difference to real cos = 6.209067e-09
For z = -3.760000e+00, k_max = 10, difference to real cos = 3.911487e-09
For z = -3.680000e+00, k_max = 10, difference to real cos = 2.439615e-09
......
For z = 3.680000e+00, k_max = 10, difference to real cos = 2.439615e-09
For z = 3.760000e+00, k_max = 10, difference to real cos = 3.911487e-09
For z = 3.840000e+00, k_max = 10, difference to real cos = 6.209067e-09
For z = 3.920000e+00, k_max = 10, difference to real cos = 9.762336e-09
true.
Не выглядит так плохо.
Приложение 3: Использование "диктов" SWI-Prolog для связи между предикатами
Я обнаружил, что при написании Perl функций это часто выгодно для передачи аргументов на основе положения с коротким замыканием и передачи одного набора пар имя-значение, то есть вместо хэшей. Это добавляет большую гибкость (именованные параметры, легко добавлять параметры, легко отлаживать, легко передавать параметры в подфункции и т. Д. c.)
Давайте попробуем это и здесь.
Это ограничено SWI-Prolog, потому что "dicts" - это функция SWI-Prolog . Подобный код делает механизм индексации Пролога бесполезным, поскольку теперь каждый предикат имеет точно такой же аргумент, Dict
, поэтому должен быть относительно медленным во время выполнения.
Только измененные предикаты
taylor_cos(Res,Z,Kmax,Verbose) :-
Zf is Z*1.0, % make a float
float(Zf),
integer(Kmax),Kmax >= 0,
Zsq is Zf*Zf,
at_element_k(taylor{ sum : Res % the result
,k : 0
,kmax : Kmax
,zsq : Zsq
,ttkf_prior : _
,ztt2k_prior : _
,verbose : Verbose }).
% ---
% Base case, when k > kmax
% ---
% We map the passed "Dict" to a sub-Dict to grab values.
% As this is "unification", not only "pattern matching" the value for
% sum "0.0" is shared into "Dict".
at_element_k(Dict) :-
taylor{ sum : 0.0
,k : K
,kmax : Kmax
,verbose : Verbose } :< Dict,
K > Kmax, % guard
!, % commit
((Verbose = verbose) ->
format("past the end as K=~d > Kmax=~d, returning back up the stack\n",[K,Kmax])
; true).
% ---
% Default case, when k <= kmax
% ---
% We map the passed "Dict" to a sub-Dict to grab values.
% We use ":<" instead of "=" so that, if the passed Dict has more values
% than expected (which can happen during program extension and fiddling),
% "partial unification" can still proceed, "=" would fail. However, no
% values can be missing!
% This gives us also the funny possibility of completely ignoring Kmax in
% the "input Dict", it doesn't appear anywhere and is still passed down
% through the recursive call. Well, it *does* appear because we print it
% out.
at_element_k(Dict) :-
taylor{ sum : SumFromKmaxBackwards % the output value, to be captured by the caller
,k : K % index of the current term/element in the Taylor sum
,kmax : Kmax % max index for which a term/element will be computed
,zsq : Zsq % z², a constant
,ttkf_prior : TTKFprior % prior "two times k factorial" i.e. (2*(k-1))!
,ztt2k_prior : ZTT2Kprior % prior "z to the 2*k" i.e. z^(2*(k-1))
,verbose : Verbose } :< Dict, % emit messages about progress if Verbose = verbose
minus_one_tothe_k(M1TTK,K), % compute (-1)^K
two_times_k_factorial(TTKF,K,TTKFprior), % compute (2*k)! based on prior value
z_tothe_2k(ZTT2K,K,Zsq,ZTT2Kprior), % compute z^(2*k) based on prior value
ElementK is M1TTK * ZTT2K / TTKF, % compute value for Taylor sum term/element at k
% (isn't there a better way to print conditionally?)
((Verbose = verbose) ->
format("element(~d) = ~e\n",[K,ElementK])
; true),
% create a NextDict from Dict for recursive call
KP1 is K+1,
put_dict( _{ sum : SumFromKmaxBackwardsPrior
,k : KP1
,ttkf_prior : TTKF
,ztt2k_prior: ZTT2K }, Dict, NextDict),
% recursive call
% (foundational thought: the procedure is really a **channel-doing-computations between the series of dicts**)
at_element_k(NextDict),
% on return, complete summing the Taylor series backwards from highest index to the current index k
SumFromKmaxBackwards is SumFromKmaxBackwardsPrior + ElementK,
% (more conditional printing)
((Verbose = verbose) ->
format("taylor-series-sum(~d ... ~d) = ~e (added ~e to prior value ~e)\n",
[K,Kmax,SumFromKmaxBackwards,ElementK,SumFromKmaxBackwardsPrior])
; true).
Это более читабельно? Я так полагаю.