% \iffalse meta-comment % %% File: l3fp-trig.dtx Copyright (C) 2011-2012 The LaTeX3 Project %% %% It may be distributed and/or modified under the conditions of the %% LaTeX Project Public License (LPPL), either version 1.3c of this %% license or (at your option) any later version. The latest version %% of this license is in the file %% %% http://www.latex-project.org/lppl.txt %% %% This file is part of the "l3kernel bundle" (The Work in LPPL) %% and all files in that bundle must be distributed together. %% %% The released version of this bundle is available from CTAN. %% %% ----------------------------------------------------------------------- %% %% The development version of the bundle can be found at %% %% http://www.latex-project.org/svnroot/experimental/trunk/ %% %% for those people who are interested. %% %%%%%%%%%%% %% NOTE: %% %%%%%%%%%%% %% %% Snapshots taken from the repository represent work in progress and may %% not work or may contain conflicting material! We therefore ask %% people _not_ to put them into distributions, archives, etc. without %% prior consultation with the LaTeX Project Team. %% %% ----------------------------------------------------------------------- %% % %<*driver> \RequirePackage{l3bootstrap} \GetIdInfo$Id: l3fp-trig.dtx 4339 2012-11-24 19:16:43Z joseph $ {L3 Floating-point trigonometric functions} \documentclass[full]{l3doc} \begin{document} \DocInput{\jobname.dtx} \end{document} % % \fi % % \title{The \textsf{l3fp-trig} package\thanks{This file % has version number \ExplFileVersion, last % revised \ExplFileDate.}\\ % Floating point trigonometric functions} % \author{^^A % The \LaTeX3 Project\thanks % {^^A % E-mail: % \href{mailto:latex-team@latex-project.org} % {latex-team@latex-project.org}^^A % }^^A % } % \date{Released \ExplFileDate} % % \maketitle % % \begin{documentation} % % \end{documentation} % % \begin{implementation} % % \section{Implementation} % % \begin{macrocode} %<*initex|package> % \end{macrocode} % % \begin{macrocode} %<@@=fp> % \end{macrocode} % %^^A todo: check EXP/rEXP everywhere. % % \subsection{Direct trigonometric functions} % % The approach for all trigonometric functions (sine, cosine, tangent, % cotangent, cosecant, and secant) is the same. % \begin{itemize} % \item Filter out special cases ($\pm 0$, $\pm\inf$ and \nan{}). % \item Keep the sign for later, and work with the absolute value % $|x|$ of the argument. % \item For numbers less than $1$, shift the significand to convert them % to fixed point numbers. Very small numbers take a slightly % different route. % \item For numbers $\geq 1$, subtract a multiple of $\pi/2$ to bring % them to the range to $[0, \pi/2]$. (This is called argument % reduction.) % \item Reduce further to $[0, \pi/4]$ using $\sin x = \cos % (\pi/2-x)$. % \item Use the appropriate power series depending on the octant % $\lfloor\frac{|x|}{\pi/4}\rfloor \mod 8$, the sign, and the % function to compute. % \end{itemize} % % \subsubsection{Sign and special numbers} % % \begin{macro}[int, EXP]{\@@_sin_o:w} % The sine of $\pm 0$ or \nan{} is the same floating point number. % The sine of $\pm\infty$ raises an invalid operation exception. % Otherwise, \cs{@@_trig_exponent:NNNNNwn} checks the exponent: if the % number is tiny, use \cs{@@_trig_epsilon_o:w} which returns % $\sin\epsilon = \epsilon$. For larger inputs, use the series % \cs{@@_sin_series:NNwww} after argument reduction. In this second % case, we will use a sign~|#2|, an initial octant of~$0$, and convert % the result of the series to a floating point directly, since % $\sin(x) = \#2 \sin\lvert x\rvert$. % \begin{macrocode} \cs_new:Npn \@@_sin_o:w \s_@@ \@@_chk:w #1#2 { \if_case:w #1 \exp_stop_f: \@@_case_return_same_o:w \or: \@@_case_use:nw { \@@_trig_exponent:NNNNNwn \@@_trig_epsilon_o:w \@@_sin_series:NNwww \@@_fixed_to_float:wN #2 \c_zero } \or: \@@_case_use:nw { \@@_invalid_operation_o:nw { sin } } \else: \@@_case_return_same_o:w \fi: \s_@@ \@@_chk:w #1#2 } % \end{macrocode} % \end{macro} % % \begin{macro}[int, EXP]{\@@_cos_o:w} % The cosine of $\pm 0$ is $1$. The cosine of $\pm\infty$ raises an % invalid operation exception. The cosine of \nan{} is itself. % Otherwise, \cs{@@_trig_exponent:NNNNNwn} checks the exponent: if the % number is tiny, use \cs{@@_trig_epsilon_one_o:w} which returns % $\cos\epsilon = 1$. For larger inputs, use the same series as for % sine, but using a positive sign~|0| and with an initial octant % of~$2$, because $\cos(x) = + \sin(\pi/2 + \lvert x\rvert)$. % \begin{macrocode} \cs_new:Npn \@@_cos_o:w \s_@@ \@@_chk:w #1#2 { \if_case:w #1 \exp_stop_f: \@@_case_return_o:Nw \c_one_fp \or: \@@_case_use:nw { \@@_trig_exponent:NNNNNwn \@@_trig_epsilon_one_o:w \@@_sin_series:NNwww \@@_fixed_to_float:wN 0 \c_two } \or: \@@_case_use:nw { \@@_invalid_operation_o:nw { cos } } \else: \@@_case_return_same_o:w \fi: \s_@@ \@@_chk:w #1#2 } % \end{macrocode} % \end{macro} % % \begin{macro}[int, EXP]{\@@_csc_o:w} % The cosecant of $\pm 0$ is $\pm \infty$ with the same sign, with a % division by zero exception (see \cs{@@_cot_zero_o:Nnw} defined % below). The cosecant of $\pm\infty$ raises an invalid operation % exception. The cosecant of \nan{} is itself. Otherwise, % \cs{@@_trig_exponent:NNNNNwn} checks the exponent: if the number is % tiny, use \cs{@@_trig_epsilon_inv_o:w} which returns $\csc\epsilon = % 1/\epsilon$. For larger inputs, use the same series as for sine, % using the sign~|#2|, a starting octant of~$0$, and inverting during % the conversion from the fixed point sine to the floating point % result, because $\csc(x) = \#2 \big( \sin\lvert x\rvert\big)^{-1}$. % \begin{macrocode} \cs_new:Npn \@@_csc_o:w \s_@@ \@@_chk:w #1#2 { \if_case:w #1 \exp_stop_f: \@@_cot_zero_o:Nnw #2 { csc } \or: \@@_case_use:nw { \@@_trig_exponent:NNNNNwn \@@_trig_epsilon_inv_o:w \@@_sin_series:NNwww \@@_fixed_inv_to_float:wN #2 \c_zero } \or: \@@_case_use:nw { \@@_invalid_operation_o:nw { csc } } \else: \@@_case_return_same_o:w \fi: \s_@@ \@@_chk:w #1#2 } % \end{macrocode} % \end{macro} % % \begin{macro}[int, EXP]{\@@_sec_o:w} % The secant of $\pm 0$ is $1$. The secant of $\pm \infty$ raises an % invalid operation exception. The secant of \nan{} is itself. % Otherwise, \cs{@@_trig_exponent:NNNNNwn} checks the exponent: if the % number is tiny, use \cs{@@_trig_epsilon_one_o:w} which returns % $\sec\epsilon = 1$. For larger inputs, use the same series as for % sine, using a positive sign~$0$, a starting octant of~$2$, and % inverting upon conversion, because $\sec(x) = + 1 / \sin(\pi/2 + % \lvert x\rvert)$. % \begin{macrocode} \cs_new:Npn \@@_sec_o:w \s_@@ \@@_chk:w #1#2 { \if_case:w #1 \exp_stop_f: \@@_case_return_o:Nw \c_one_fp \or: \@@_case_use:nw { \@@_trig_exponent:NNNNNwn \@@_trig_epsilon_one_o:w \@@_sin_series:NNwww \@@_fixed_inv_to_float:wN 0 \c_two } \or: \@@_case_use:nw { \@@_invalid_operation_o:nw { sec } } \else: \@@_case_return_same_o:w \fi: \s_@@ \@@_chk:w #1#2 } % \end{macrocode} % \end{macro} % % \begin{macro}[int, EXP]{\@@_tan_o:w} % The tangent of $\pm 0$ or \nan{} is the same floating point number. % The tangent of $\pm\infty$ raises an invalid operation exception. % Otherwise, \cs{@@_trig_exponent:NNNNNwn} checks the exponent: if the % number is tiny, use \cs{@@_trig_epsilon_o:w} which returns % $\tan\epsilon = \epsilon$. For larger inputs, use % \cs{@@_tan_series_o:NNwww} for the calculation after argument % reduction, with a sign~|#2| and an initial octant of~$1$ (this shift % is somewhat arbitrary). See \cs{@@_cot_o:w} for an explanation of % the $0$~argument. % \begin{macrocode} \cs_new:Npn \@@_tan_o:w \s_@@ \@@_chk:w #1#2 { \if_case:w #1 \exp_stop_f: \@@_case_return_same_o:w \or: \@@_case_use:nw { \@@_trig_exponent:NNNNNwn \@@_trig_epsilon_o:w \@@_tan_series_o:NNwww 0 #2 \c_one } \or: \@@_case_use:nw { \@@_invalid_operation_o:nw { tan } } \else: \@@_case_return_same_o:w \fi: \s_@@ \@@_chk:w #1#2 } % \end{macrocode} % \end{macro} % % \begin{macro}[int, EXP]{\@@_cot_o:w} % \begin{macro}[aux, EXP]{\@@_cot_zero_o:Nnw} % The cotangent of $\pm 0$ is $\pm \infty$ with the same sign, with a % division by zero exception (see \cs{@@_cot_zero_o:Nnw}. The % cotangent of $\pm\infty$ raises an invalid operation exception. The % cotangent of \nan{} is itself. We use $\cot x = - \tan (\pi/2 + % x)$, and the initial octant for the tangent was chosen to be $1$, so % the octant here starts at $3$. The change in sign is obtained by % feeding \cs{@@_tan_series_o:NNwww} two signs rather than just the sign % of the argument: the first of those indicates whether we compute % tangent or cotangent. Those signs are eventually combined. % \begin{macrocode} \cs_new:Npn \@@_cot_o:w \s_@@ \@@_chk:w #1#2 { \if_case:w #1 \exp_stop_f: \@@_cot_zero_o:Nnw #2 { cot } \or: \@@_case_use:nw { \@@_trig_exponent:NNNNNwn \@@_trig_epsilon_inv_o:w \@@_tan_series_o:NNwww 2 #2 \c_three } \or: \@@_case_use:nw { \@@_invalid_operation_o:nw { cot } } \else: \@@_case_return_same_o:w \fi: \s_@@ \@@_chk:w #1#2 } \cs_new:Npn \@@_cot_zero_o:Nnw #1 #2 #3 \fi: { \fi: \if_meaning:w 0 #1 \exp_after:wN \@@_division_by_zero_o:Nnw \exp_after:wN \c_inf_fp \else: \exp_after:wN \@@_division_by_zero_o:Nnw \exp_after:wN \c_minus_inf_fp \fi: {#2} } % \end{macrocode} % \end{macro} % \end{macro} % % \subsubsection{Small and tiny arguments} % % \begin{macro}[aux, EXP]{\@@_trig_exponent:NNNNNwn} % The first five arguments control what trigonometric function we % compute, then follows a normal floating point number. If the % floating point is smaller than $10^{-8}$, then call the % \texttt{_epsilon} auxiliary~|#1|. Otherwise, call the function % |#2|, with arguments |#3|; |#4|; the octant, computed in an integer % expression starting with |#5| and stopped by a period; and a fixed % point number obtained from the floating point number by argument % reduction. Argument reduction leaves a shift into the integer % expression for the octant. Numbers less than~$1$ are converted % using \cs{@@_trig_small:w} which simply shifts the significand, while % large numbers need argument reduction. % \begin{macrocode} \cs_new:Npn \@@_trig_exponent:NNNNNwn #1#2#3#4#5 \s_@@ \@@_chk:w 1#6#7 { \if_int_compare:w #7 > - \c_eight \exp_after:wN #2 \exp_after:wN #3 \exp_after:wN #4 \int_use:N \__int_eval:w #5 \if_int_compare:w #7 > \c_zero \exp_after:wN \@@_trig_large:ww \__int_value:w \else: \exp_after:wN \@@_trig_small:ww \__int_value:w \fi: \else: \exp_after:wN #1 \exp_after:wN #6 \fi: #7 ; } % \end{macrocode} % \end{macro} % % \begin{macro}[aux, EXP] % {\@@_trig_epsilon_o:w, \@@_trig_epsilon_one_o:w, \@@_trig_epsilon_inv_o:w} % Sine and tangent of tiny numbers give the number itself: the % relative error is less than $5 \cdot 10^{-17}$, which is % appropriate. Cosine and secant simply give~$1$. Cotangent and % cosecant compute $1/\epsilon$. This is actually slightly wrong % because further terms in the power series could affect the rounding % for cotangent. % \begin{macrocode} \cs_new:Npn \@@_trig_epsilon_o:w #1 #2 ; { \@@_exp_after_o:w \s_@@ \@@_chk:w 1 #1 {#2} } \cs_new:Npn \@@_trig_epsilon_one_o:w #1 ; #2 ; { \exp_after:wN \c_one_fp } \group_begin: \char_set_catcode_letter:N / \cs_new:Npn \@@_trig_epsilon_inv_o:w #1 #2 ; { \exp_after:wN \@@_/_o:ww \c_one_fp \s_@@ \@@_chk:w 1 #1 {#2} } \group_end: % \end{macrocode} % \end{macro} % % \begin{macro}[aux, EXP]{\@@_trig_small:ww} % Floating point numbers less than $1$ are converted to fixed point % numbers by prepending a number of zeroes to the significand. Since we % have already filtered out numbers less than $10^{-8}$, we add at % most $7$ zeroes, hence no digit is lost in converting to a fixed % point number. % \begin{macrocode} \cs_new:Npn \@@_trig_small:ww #1; #2#3#4#5; { \exp_after:wN \@@_pack_twice_four:wNNNNNNNN \exp_after:wN \@@_pack_twice_four:wNNNNNNNN \exp_after:wN \@@_pack_twice_four:wNNNNNNNN \exp_after:wN . \exp_after:wN ; \tex_romannumeral:D -`0 \prg_replicate:nn { - #1 } { 0 } #2#3#4#5 0000 0000 ; } % \end{macrocode} % \end{macro} % % \subsubsection{Reduction of large arguments} % % In the case of a floating point argument greater or equal to $1$, we % need to perform argument reduction. % % \begin{macro}[aux, rEXP] % { % \@@_trig_large:ww, \@@_trig_large:www, % \@@_trig_large_o:wnnnn, \@@_trig_large_break:w % } % We shift the significand by one digit at a time, subtracting a multiple % of $2\pi$ at each step. We use a value of $2\pi$ rounded up, % consistent with the choice of \cs{c_pi_fp}. This is not quite % correct from an accuracy perspective, but has the nice property that % $\sin(180\mathrm{deg}) = 0$ exactly. The arguments of % \cs{@@_trig_large:www} are a leading block of up to $5$ digits, % three brace groups of $4$ digits each, and the exponent, decremented % at each step. The multiple of $2\pi$ to subtract is estimated as % $\lfloor |#1| / 6283\rfloor$ (the formula chosen always gives a % non-negative integer). The subtraction has a form similar to our % usual multiplications (see \pkg{l3fp-basics} or % \pkg{l3fp-extended}). Once the exponent reaches $0$, we are done % subtracting $2\pi$, and we call \cs{@@_trig_octant_loop:nnnnnw} to do % the reduction by $\pi/2$. % \begin{macrocode} \cs_new:Npn \@@_trig_large:ww #1; #2#3; { \@@_trig_large:www #2; #3 ; #1; } \cs_new:Npn \@@_trig_large:www #1; #2; #3; { \if_meaning:w 0 #3 \@@_trig_large_break:w \fi: \exp_after:wN \@@_trig_large_o:wnnnn \int_use:N \__int_eval:w ( #1 - 3141 ) / 6283 ; {#1} #2 \exp_after:wN ; \int_use:N \__int_eval:w \c_minus_one + #3; } \cs_new:Npn \@@_trig_large_o:wnnnn #1; #2#3#4#5 { \exp_after:wN \@@_trig_large:www \int_use:N \__int_eval:w \c_@@_leading_shift_int + #20 - #1*62831 \exp_after:wN \@@_pack:NNNNNw \int_use:N \__int_eval:w \c_@@_middle_shift_int + #30 - #1*8530 \exp_after:wN \@@_pack:NNNNNw \int_use:N \__int_eval:w \c_@@_middle_shift_int + #40 - #1*7179 \exp_after:wN \@@_pack:NNNNNw \int_use:N \__int_eval:w \c_@@_trailing_shift_int + #50 - #1*5880 \exp_after:wN ; } \cs_new:Npn \@@_trig_large_break:w \fi: #1; #2; { \fi: \@@_trig_octant_loop:nnnnnw #2 {0000} {0000} ; } % \end{macrocode} % \end{macro} % % \begin{macro}[aux, rEXP] % {\@@_trig_octant_loop:nnnnnw, \@@_trig_octant_break:w} % We receive a fixed point number as argument. As long as it is % greater than half of \cs{c_pi_fp}, namely $1.5707963267948970$, % subtract that fixed-point approximation of $\pi/2$, and leave |+| % |\c_two| in the integer expression for the octant. Once the argument % becomes smaller, break the initial loop. If the number is greater % than $0.7854$ (overestimate of $\pi/4$), then compute $\pi/2 - x$ % and increment the octant. The result is in all cases in the range % $[0, 0.7854]$, appropriate for the series expansions. % \begin{macrocode} \cs_new:Npn \@@_trig_octant_loop:nnnnnw #1#2#3#4#5#6; { \if_int_compare:w #1#2 < 157079633 \exp_stop_f: \if_int_compare:w #1#2 = 157079632 \exp_stop_f: \if_int_compare:w #3#4 > 67948969 \exp_stop_f: \use_i_ii:nnn \fi: \fi: \@@_trig_octant_break:w \fi: + \c_two \@@_fixed_sub:wwn {#1} {#2} {#3} {#4} {0000} {0000} ; {15707} {9632} {6794} {8970} {0000} {0000} ; \@@_trig_octant_loop:nnnnnw } \cs_new:Npn \@@_trig_octant_break:w #1 \fi: + #2#3 #4#5; #6; #7; { \fi: \if_int_compare:w #4 < 7854 \exp_stop_f: \exp_after:wN \@@_use_i_until_s:nw \exp_after:wN . \fi: + \c_one \@@_fixed_sub:wwn #6 ; {#4} #5 ; . ; } % \end{macrocode} % \end{macro} % % \subsection{Computing the power series} % % \begin{macro}[aux, EXP]{\@@_sin_series:NNwww, \@@_sin_series_aux:NNnww} % Here we receive a conversion function \cs{@@_fixed_to_float:wN} or % \cs{@@_fixed_inv_to_float:wN}, a \meta{sign} ($0$ or $2$), a % (non-negative) \meta{octant} delimited by a dot, a \meta{fixed % point} number, and junk delimited by a semicolon. The auxiliary % receives: % \begin{itemize} % \item The final sign, which depends on the octant |#3| and the % original sign |#2|, % \item The octant |#3|, which will control the series we use. % \item The square |#4 * #4| of the argument, computed with % \cs{@@_fixed_mul:wwn}. % \item The number itself. % \end{itemize} % If the octant is in $\{1,2,5,6,\ldots{}\}$, we are near an extremum % of the function and we use the series % \[ % \cos(x) = 1 - x^2 \bigg( \frac{1}{2!} - x^2 \bigg( \frac{1}{4!} % - x^2 \bigg( \cdots \bigg) \bigg) \bigg) . % \] % Otherwise, the series % \[ % \sin(x) = x \bigg( 1 - x^2 \bigg( \frac{1}{3!} - x^2 \bigg( % \frac{1}{5!} - x^2 \bigg( \cdots \bigg) \bigg) \bigg) \bigg) % \] % is used. Finally, the fixed point number is converted to a floating % point number with the given sign, and \cs{@@_sanitize:Nw} checks for % overflow and underflow. % \begin{macrocode} \cs_new:Npn \@@_sin_series:NNwww #1#2#3 . #4; #5; { \@@_fixed_mul:wwn #4; #4; { \exp_after:wN \@@_sin_series_aux:NNnww \exp_after:wN #1 \__int_value:w \if_int_odd:w \__int_eval:w ( #3 + \c_two ) / \c_four \__int_eval_end: #2 \else: \if_meaning:w #2 0 2 \else: 0 \fi: \fi: {#3} } #4 ; } \cs_new:Npn \@@_sin_series_aux:NNnww #1#2#3 #4; #5; { \if_int_odd:w \__int_eval:w #3 / \c_two \__int_eval_end: \exp_after:wN \use_i:nn \else: \exp_after:wN \use_ii:nn \fi: { % 1/18! \@@_fixed_mul_sub_back:wwwn {0000}{0000}{0000}{0001}{5619}{2070}; #4; {0000}{0000}{0000}{0477}{9477}{3324}; \@@_fixed_mul_sub_back:wwwn #4; {0000}{0000}{0011}{4707}{4559}{7730}; \@@_fixed_mul_sub_back:wwwn #4; {0000}{0000}{2087}{6756}{9878}{6810}; \@@_fixed_mul_sub_back:wwwn #4; {0000}{0027}{5573}{1922}{3985}{8907}; \@@_fixed_mul_sub_back:wwwn #4; {0000}{2480}{1587}{3015}{8730}{1587}; \@@_fixed_mul_sub_back:wwwn #4; {0013}{8888}{8888}{8888}{8888}{8889}; \@@_fixed_mul_sub_back:wwwn #4; {0416}{6666}{6666}{6666}{6666}{6667}; \@@_fixed_mul_sub_back:wwwn #4; {5000}{0000}{0000}{0000}{0000}{0000}; \@@_fixed_mul_sub_back:wwwn #4;{10000}{0000}{0000}{0000}{0000}{0000}; } { % 1/17! \@@_fixed_mul_sub_back:wwwn {0000}{0000}{0000}{0028}{1145}{7254}; #4; {0000}{0000}{0000}{7647}{1637}{3182}; \@@_fixed_mul_sub_back:wwwn #4; {0000}{0000}{0160}{5904}{3836}{8216}; \@@_fixed_mul_sub_back:wwwn #4; {0000}{0002}{5052}{1083}{8544}{1719}; \@@_fixed_mul_sub_back:wwwn #4; {0000}{0275}{5731}{9223}{9858}{9065}; \@@_fixed_mul_sub_back:wwwn #4; {0001}{9841}{2698}{4126}{9841}{2698}; \@@_fixed_mul_sub_back:wwwn #4; {0083}{3333}{3333}{3333}{3333}{3333}; \@@_fixed_mul_sub_back:wwwn #4; {1666}{6666}{6666}{6666}{6666}{6667}; \@@_fixed_mul_sub_back:wwwn #4;{10000}{0000}{0000}{0000}{0000}{0000}; \@@_fixed_mul:wwn #5; } { \exp_after:wN \@@_sanitize:Nw \exp_after:wN #2 \int_use:N \__int_eval:w #1 } #2 } % \end{macrocode} % \end{macro} % % \begin{macro}[aux, EXP]{\@@_tan_series_o:NNwww, \@@_tan_series_aux_o:Nnww} % Contrarily to \cs{@@_sin_series:NNwww} which received the conversion % auxiliary as |#1|, here |#1| is $0$ for tangent, and $2$ for % cotangent. Consider first the case of the tangent. The octant |#3| % starts at $1$, which means that it is $1$ or $2$ for $\lvert % x\rvert\in[0,\pi/2]$, it is $3$ or $4$ for $\lvert % x\rvert\in[\pi/2,\pi]$, and so on: the intervals on which % $\tan\lvert x\rvert\geq 0$ coincide with those for which $\lfloor % (|#3| + 1) / 2\rfloor$ is odd. We also have to take into account % the original sign of $x$ to get the sign of the final result; it is % straightforward to check that the first \cs{__int_value:w} expansion % produces $0$ for a positive final result, and $2$ otherwise. A % similar story holds for $\cot(x)$. % % The auxiliary receives the sign, the octant, the square of the % (reduced) input, and the (reduced) input as arguments. It then % computes the numerator and denominator of % \[ % \tan(x) \simeq % \frac{x (1 - x^2 (a_1 - x^2 (a_2 - x^2 (a_3 - x^2 (a_4 - x^2 a_5)))))} % {1 - x^2 (b_1 - x^2 (b_2 - x^2 (b_3 - x^2 (b_4 - x^2 b_5))))} . % \] % The ratio itself is computed by \cs{@@_fixed_div_to_float:ww}, which % converts it directly to a floating point number to avoid rounding % issues. For octants~|#2| (really, quadrants) next to a pole of the % functions, the fixed point numerator and denominator are exchanged % before computing the ratio. Note that this \cs{if_int_odd:w} test % relies on the fact that the octant is at least~$1$. % \begin{macrocode} \cs_new:Npn \@@_tan_series_o:NNwww #1#2#3. #4; #5; { \@@_fixed_mul:wwn #4; #4; { \exp_after:wN \@@_tan_series_aux_o:Nnww \__int_value:w \if_int_odd:w \__int_eval:w #3 / \c_two \__int_eval_end: \exp_after:wN \reverse_if:N \fi: \if_meaning:w #1#2 2 \else: 0 \fi: {#3} } #4 ; } \cs_new:Npn \@@_tan_series_aux_o:Nnww #1 #2 #3; #4; { \@@_fixed_mul_sub_back:wwwn {0000}{0000}{1527}{3493}{0856}{7059}; #3; {0000}{0159}{6080}{0274}{5257}{6472}; \@@_fixed_mul_sub_back:wwwn #3; {0002}{4571}{2320}{0157}{2558}{8481}; \@@_fixed_mul_sub_back:wwwn #3; {0115}{5830}{7533}{5397}{3168}{2147}; \@@_fixed_mul_sub_back:wwwn #3; {1929}{8245}{6140}{3508}{7719}{2982}; \@@_fixed_mul_sub_back:wwwn #3;{10000}{0000}{0000}{0000}{0000}{0000}; \@@_fixed_mul:wwn #4; { \@@_fixed_mul_sub_back:wwwn {0000}{0007}{0258}{0681}{9408}{4706}; #3; {0000}{2343}{7175}{1399}{6151}{7670}; \@@_fixed_mul_sub_back:wwwn #3; {0019}{2638}{4588}{9232}{8861}{3691}; \@@_fixed_mul_sub_back:wwwn #3; {0536}{6357}{0691}{4344}{6852}{4252}; \@@_fixed_mul_sub_back:wwwn #3; {5263}{1578}{9473}{6842}{1052}{6315}; \@@_fixed_mul_sub_back:wwwn #3;{10000}{0000}{0000}{0000}{0000}{0000}; { \exp_after:wN \@@_sanitize:Nw \exp_after:wN #1 \int_use:N \__int_eval:w \reverse_if:N \if_int_odd:w \__int_eval:w (#2 - \c_one) / \c_two \__int_eval_end: \exp_after:wN \@@_reverse_args:Nww \fi: \@@_fixed_div_to_float:ww } } } % \end{macrocode} % \end{macro} % % \begin{macrocode} % % \end{macrocode} % % \end{implementation} % % \PrintChanges % % \PrintIndex