diff options
Diffstat (limited to 'Master/texmf-dist/source/latex/l3kernel/l3fp-trig.dtx')
-rw-r--r-- | Master/texmf-dist/source/latex/l3kernel/l3fp-trig.dtx | 1569 |
1 files changed, 1255 insertions, 314 deletions
diff --git a/Master/texmf-dist/source/latex/l3kernel/l3fp-trig.dtx b/Master/texmf-dist/source/latex/l3kernel/l3fp-trig.dtx index 34f0a220acd..0fea2b00541 100644 --- a/Master/texmf-dist/source/latex/l3kernel/l3fp-trig.dtx +++ b/Master/texmf-dist/source/latex/l3kernel/l3fp-trig.dtx @@ -1,6 +1,6 @@ % \iffalse meta-comment % -%% File: l3fp-trig.dtx Copyright (C) 2011-2012 The LaTeX3 Project +%% File: l3fp-trig.dtx Copyright (C) 2011-2013 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 @@ -36,7 +36,7 @@ % %<*driver> \RequirePackage{l3bootstrap} -\GetIdInfo$Id: l3fp-trig.dtx 4593 2013-10-07 13:32:35Z bruno $ +\GetIdInfo$Id: l3fp-trig.dtx 4607 2013-11-19 08:35:08Z joseph $ {L3 Floating-point trigonometric functions} \documentclass[full]{l3doc} \begin{document} @@ -67,7 +67,7 @@ % % \begin{implementation} % -% \section{Implementation} +% \section{\pkg{l3fp-trig} Implementation} % % \begin{macrocode} %<*initex|package> @@ -82,51 +82,58 @@ % \subsection{Direct trigonometric functions} % % The approach for all trigonometric functions (sine, cosine, tangent, -% cotangent, cosecant, and secant) is the same. +% cotangent, cosecant, and secant), with arguments given in radians or +% in degrees, 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)$. +% $\lvert x\rvert$ of the argument. +% \item Small numbers ($\lvert x\rvert<1$ in radians, $\lvert +% x\rvert<10$ in degrees) are converted to fixed point numbers (and +% to radians if $\lvert x\rvert$ is in degrees). +% \item For larger numbers, we need argument reduction. Subtract a +% multiple of $\pi/2$ (in degrees,~$90$) to bring the number to the +% range to $[0, \pi/2)$ (in degrees, $[0,90)$). +% \item Reduce further to $[0, \pi/4]$ (in degrees, $[0,45]$) using +% $\sin x = \cos (\pi/2-x)$, and when working in degrees, convert to +% radians. % \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. +% $\lfloor\frac{|x|}{\pi/4}\rfloor \mod 8$ (in degrees, the same +% formula with $\pi/4\to 45$), the sign, and the function to +% compute. % \end{itemize} % -% \subsubsection{Sign and special numbers} +% \subsubsection{Filtering special cases} % % \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: +% This function, and its analogs for \texttt{cos}, \texttt{csc}, +% \texttt{sec}, \texttt{tan}, and \texttt{cot} instead of +% \texttt{sin}, are followed either by \cs{use_i:nn} and a float in +% radians or by \cs{use_ii:nn} and a float in degrees. The sine of +% $\pm 0$ or \nan{} is the same float. The sine of $\pm\infty$ raises +% an invalid operation exception with the appropriate function name. +% Otherwise, call the \texttt{trig} function to perform argument +% reduction and if necessary convert the reduced argument to radians. +% Then, \cs{@@_sin_series_o:NNwwww} will be called to compute the +% Taylor series: this function receives a sign~|#3|, an initial octant +% of~$0$, and the function \cs{@@_ep_to_float:wwN} which converts the +% result of the series to a floating point directly rather than taking +% its inverse, since $\sin(x) = \#3 \sin\lvert x\rvert$. +% \begin{macrocode} +\cs_new:Npn \@@_sin_o:w #1 \s_@@ \@@_chk:w #2#3#4; @ + { + \if_case:w #2 \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 } } + \or: \@@_case_use:nw + { + \@@_trig:NNNNNwn #1 \@@_sin_series_o:NNwwww + \@@_ep_to_float:wwN #3 \c_zero + } + \or: \@@_case_use:nw + { \@@_invalid_operation_o:fw { #1 { sin } { sind } } } \else: \@@_case_return_same_o:w \fi: - \s_@@ \@@_chk:w #1#2 + \s_@@ \@@_chk:w #2 #3 #4; } % \end{macrocode} % \end{macro} @@ -134,56 +141,56 @@ % \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 +% Otherwise, the \texttt{trig} function reduces the argument to at +% most half a right-angle and converts if necessary to radians. We +% will then call the same series as for sine, but using a positive +% sign~|0| regardless of the sign of~$x$, 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 +\cs_new:Npn \@@_cos_o:w #1 \s_@@ \@@_chk:w #2#3; @ { - \if_case:w #1 \exp_stop_f: + \if_case:w #2 \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 } } + \or: \@@_case_use:nw + { + \@@_trig:NNNNNwn #1 \@@_sin_series_o:NNwwww + \@@_ep_to_float:wwN 0 \c_two + } + \or: \@@_case_use:nw + { \@@_invalid_operation_o:fw { #1 { cos } { cosd } } } \else: \@@_case_return_same_o:w \fi: - \s_@@ \@@_chk:w #1#2 + \s_@@ \@@_chk:w #2 #3; } % \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 } } +% division by zero exception (see \cs{@@_cot_zero_o:Nfw} defined +% below), which requires the function name. The cosecant of +% $\pm\infty$ raises an invalid operation exception. The cosecant of +% \nan{} is itself. Otherwise, the \texttt{trig} function performs +% the argument reduction, and converts if necessary to radians before +% calling the same series as for sine, using the sign~|#3|, a starting +% octant of~$0$, and inverting during the conversion from the fixed +% point sine to the floating point result, because $\csc(x) = \#3 +% \big( \sin\lvert x\rvert\big)^{-1}$. +% \begin{macrocode} +\cs_new:Npn \@@_csc_o:w #1 \s_@@ \@@_chk:w #2#3#4; @ + { + \if_case:w #2 \exp_stop_f: + \@@_cot_zero_o:Nfw #3 { #1 { csc } { cscd } } + \or: \@@_case_use:nw + { + \@@_trig:NNNNNwn #1 \@@_sin_series_o:NNwwww + \@@_ep_inv_to_float:wwN #3 \c_zero + } + \or: \@@_case_use:nw + { \@@_invalid_operation_o:fw { #1 { csc } { cscd } } } \else: \@@_case_return_same_o:w \fi: - \s_@@ \@@_chk:w #1#2 + \s_@@ \@@_chk:w #2 #3 #4; } % \end{macrocode} % \end{macro} @@ -191,27 +198,25 @@ % \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)$. +% Otherwise, the \texttt{trig} function reduces the argument and turns +% it to radians before calling 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 +\cs_new:Npn \@@_sec_o:w #1 \s_@@ \@@_chk:w #2#3; @ { - \if_case:w #1 \exp_stop_f: + \if_case:w #2 \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 } } + \or: \@@_case_use:nw + { + \@@_trig:NNNNNwn #1 \@@_sin_series_o:NNwwww + \@@_ep_inv_to_float:wwN 0 \c_two + } + \or: \@@_case_use:nw + { \@@_invalid_operation_o:fw { #1 { sec } { secd } } } \else: \@@_case_return_same_o:w \fi: - \s_@@ \@@_chk:w #1#2 + \s_@@ \@@_chk:w #2 #3; } % \end{macrocode} % \end{macro} @@ -219,263 +224,606 @@ % \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. +% Once more, the \texttt{trig} function does the argument reduction +% step and conversion to radians before calling +% \cs{@@_tan_series_o:NNwwww}, with a sign~|#3| 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 +\cs_new:Npn \@@_tan_o:w #1 \s_@@ \@@_chk:w #2#3#4; @ { - \if_case:w #1 \exp_stop_f: + \if_case:w #2 \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 } } + \or: \@@_case_use:nw + { + \@@_trig:NNNNNwn #1 + \@@_tan_series_o:NNwwww 0 #3 \c_one + } + \or: \@@_case_use:nw + { \@@_invalid_operation_o:fw { #1 { tan } { tand } } } \else: \@@_case_return_same_o:w \fi: - \s_@@ \@@_chk:w #1#2 + \s_@@ \@@_chk:w #2 #3 #4; } % \end{macrocode} % \end{macro} % % \begin{macro}[int, EXP]{\@@_cot_o:w} -% \begin{macro}[aux, EXP]{\@@_cot_zero_o:Nnw} +% \begin{macro}[aux, EXP]{\@@_cot_zero_o:Nfw} % 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 +% division by zero exception (see \cs{@@_cot_zero_o:Nfw}. 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. +% feeding \cs{@@_tan_series_o:NNwwww} 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 +\cs_new:Npn \@@_cot_o:w #1 \s_@@ \@@_chk:w #2#3#4; @ { - \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 } } + \if_case:w #2 \exp_stop_f: + \@@_cot_zero_o:Nfw #3 { #1 { cot } { cotd } } + \or: \@@_case_use:nw + { + \@@_trig:NNNNNwn #1 + \@@_tan_series_o:NNwwww 2 #3 \c_three + } + \or: \@@_case_use:nw + { \@@_invalid_operation_o:fw { #1 { cot } { cotd } } } \else: \@@_case_return_same_o:w \fi: - \s_@@ \@@_chk:w #1#2 + \s_@@ \@@_chk:w #2 #3 #4; } -\cs_new:Npn \@@_cot_zero_o:Nnw #1 #2 #3 \fi: +\cs_new:Npn \@@_cot_zero_o:Nfw #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: + \token_if_eq_meaning:NNTF 0 #1 + { \exp_args:NNf \@@_division_by_zero_o:Nnw \c_inf_fp } + { \exp_args:NNf \@@_division_by_zero_o:Nnw \c_minus_inf_fp } {#2} } % \end{macrocode} % \end{macro} % \end{macro} % -% \subsubsection{Small and tiny arguments} +% \subsubsection{Distinguishing small and large 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{macro}[aux, EXP]{\@@_trig:NNNNNwn} +% The first argument is \cs{use_i:nn} if the operand is in radians and +% \cs{use_ii:nn} if it is in degrees. Arguments |#2| to~|#5| control +% what trigonometric function we compute, and |#6| to~|#8| are pieces +% of a normal floating point number. Call the \texttt{_series} +% function~|#2|, with arguments |#3|, either a conversion function +% (\cs{@@_ep_to_float:wN} or \cs{@@_ep_inv_to_float:wN}) or a sign $0$ +% or~$2$ when computing tangent or cotangent; |#4|, a sign $0$ or~$2$; +% 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 (if necessary) and +% conversion to radians (if necessary). Any argument reduction +% adjusts the octant accordingly by leaving a (positive) shift into +% its integer expression. Let us explain the integer comparison. Two +% of the four \cs{exp_after:wN} are expanded, the expansion hits the +% test, which is true if the float is at least~$1$ when working in +% radians, and at least $10$ when working in degrees. Then one of the +% remaining \cs{exp_after:wN} hits |#1|, which picks the \texttt{trig} +% or \texttt{trigd} function in whichever branch of the conditional +% was taken. The final \cs{exp_after:wN} closes the conditional. At +% the end of the day, a number is \texttt{large} if it is $\geq 1$ in +% radians or $\geq 10$ in degrees, and \texttt{small} otherwise. All +% four \texttt{trig}/\texttt{trigd} auxiliaries receive the operand as +% an extended-precision number. % \begin{macrocode} -\cs_new:Npn \@@_trig_exponent:NNNNNwn #1#2#3#4#5 \s_@@ \@@_chk:w 1#6#7 +\cs_new:Npn \@@_trig:NNNNNwn #1#2#3#4#5 \s_@@ \@@_chk:w 1#6#7#8; { - \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 ; + \exp_after:wN #2 + \exp_after:wN #3 + \exp_after:wN #4 + \int_use:N \__int_eval:w #5 + \exp_after:wN \exp_after:wN \exp_after:wN \exp_after:wN + \if_int_compare:w #7 > #1 \c_zero \c_one + #1 \@@_trig_large:ww \@@_trigd_large:ww + \else: + #1 \@@_trig_small:ww \@@_trigd_small:ww + \fi: + #7,#8{0000}{0000}; } % \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: +% \subsubsection{Small arguments} +% +% \begin{macro}[aux, EXP]{\@@_trig_small:ww} +% This receives a small extended-precision number in radians and +% converts it to a fixed point number. Some trailing digits may be +% lost in the conversion, so we keep the original floating point +% number around: when computing sine or tangent (or their inverses), +% the last step will be to multiply by the floating point number (as +% an extended-precision number) rather than the fixed point number. +% The period serves to end the integer expression for the octant. +% \begin{macrocode} +\cs_new:Npn \@@_trig_small:ww #1,#2; + { \@@_ep_to_fixed:wwn #1,#2; . #1,#2; } % \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{macro}[aux, EXP]{\@@_trigd_small:ww} +% Convert the extended-precision number to radians, then call +% \cs{@@_trig_small:ww} to massage it in the form appropriate for the +% \texttt{_series} auxiliary. % \begin{macrocode} -\cs_new:Npn \@@_trig_small:ww #1; #2#3#4#5; +\cs_new:Npn \@@_trigd_small:ww #1,#2; { + \@@_ep_mul_raw:wwwwN + -1,{1745}{3292}{5199}{4329}{5769}{2369}; #1,#2; + \@@_trig_small:ww + } +% \end{macrocode} +% \end{macro} +% +% \subsubsection{Argument reduction in degrees} +% +% \begin{macro}[aux, rEXP] +% { +% \@@_trigd_large:ww, \@@_trigd_large_auxi:nnnnwNNNN, +% \@@_trigd_large_auxii:wNw, \@@_trigd_large_auxiii:www +% } +% Note that $25\times 360 = 9000$, so $10^{k+1} \equiv 10^{k} +% \pmod{360}$ for $k\geq 3$. When the exponent~|#1| is very large, we +% can thus safely replace it by~$22$ (or even~$19$). We turn the +% floating point number into a fixed point number with two blocks of +% $8$~digits followed by five blocks of $4$~digits. The original +% float is $100\times\meta{block_1}\cdots\meta{block_3}. +% \meta{block_4}\cdots\meta{block_7}$, or is equal to it modulo~$360$ +% if the exponent~|#1| is very large. The first auxiliary finds +% $\meta{block_1} + \meta{block_2} \pmod{9}$, a single digit, and +% prepends it to the $4$~digits of \meta{block_3}. It also unpacks +% \meta{block_4} and grabs the $4$~digits of \meta{block_7}. The +% second auxiliary grabs the \meta{block_3} plus any contribution from +% the first two blocks as~|#1|, the first digit of \meta{block_4} +% (just after the decimal point in hundreds of degrees) as~|#2|, and +% the three other digits as~|#3|. It finds the quotient and remainder +% of |#1#2| modulo~$9$, adds twice the quotient to the integer +% expression for the octant, and places the remainder (between $0$ +% and~$8$) before |#3| to form a new \meta{block_4}. The resulting +% fixed point number is $x\in [0, 0.9]$. If $x\geq 0.45$, we add~$1$ +% to the octant and feed $0.9-x$ with an exponent of~$2$ (to +% compensate the fact that we are working in units of hundreds of +% degrees rather than degrees) to \cs{@@_trigd_small:ww}. Otherwise, +% we feed it~$x$ with an exponent of~$2$. The third auxiliary also +% discards digits which were not packed into the various +% \meta{blocks}. Since the original exponent~|#1| is at least~$2$, +% those are all~$0$ and no precision is lost (|#6| and~|#7| are +% four~$0$ each). +% \begin{macrocode} +\cs_new:Npn \@@_trigd_large:ww #1, #2#3#4#5#6#7; + { + \exp_after:wN \@@_pack_eight:wNNNNNNNN + \exp_after:wN \@@_pack_eight:wNNNNNNNN \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 \@@_trigd_large_auxi:nnnnwNNNN \exp_after:wN ; \tex_romannumeral:D -`0 - \prg_replicate:nn { - #1 } { 0 } #2#3#4#5 0000 0000 ; + \prg_replicate:nn { \int_max:nn { 22 - #1 } { 0 } } { 0 } + #2#3#4#5#6#7 0000 0000 0000 ! + } +\cs_new:Npn \@@_trigd_large_auxi:nnnnwNNNN #1#2#3#4#5; #6#7#8#9 + { + \exp_after:wN \@@_trigd_large_auxii:wNw + \int_use:N \__int_eval:w #1 + #2 + - (#1 + #2 - \c_four) / \c_nine * \c_nine \__int_eval_end: + #3; + #4; #5{#6#7#8#9}; + } +\cs_new:Npn \@@_trigd_large_auxii:wNw #1; #2#3; + { + + (#1#2 - \c_four) / \c_nine * \c_two + \exp_after:wN \@@_trigd_large_auxiii:www + \int_use:N \__int_eval:w #1#2 + - (#1#2 - \c_four) / \c_nine * \c_nine \__int_eval_end: #3 ; + } +\cs_new:Npn \@@_trigd_large_auxiii:www #1; #2; #3! + { + \if_int_compare:w #1 < 4500 \exp_stop_f: + \exp_after:wN \@@_use_i_until_s:nw + \exp_after:wN \@@_fixed_continue:wn + \else: + + \c_one + \fi: + \@@_fixed_sub:wwn {9000}{0000}{0000}{0000}{0000}{0000}; + {#1}#2{0000}{0000}; + { \@@_trigd_small:ww 2, } } % \end{macrocode} % \end{macro} % -% \subsubsection{Reduction of large arguments} +% \subsubsection{Argument reduction in radians} % -% In the case of a floating point argument greater or equal to $1$, we -% need to perform argument reduction. +% Arguments greater or equal to~$1$ need to be reduced to a range where +% we only need a few terms of the Taylor series. We reduce to the range +% $[0,2\pi]$ by subtracting multiples of~$2\pi$, then to the smaller +% range $[0,\pi/2]$ by subtracting multiples of~$\pi/2$ (keeping track +% of how many times~$\pi/2$ is subtracted), then to $[0,\pi/4]$ by +% mapping $x\to \pi/2 - x$ if appropriate. When the argument is very +% large, say, $10^{100}$, an equally large multiple of~$2\pi$ must be +% subtracted, hence we must work with a very good approximation +% of~$2\pi$ in order to get a sensible remainder modulo~$2\pi$. +% +% Specifically, we multiply the argument by an approximation +% of~$1/(2\pi)$ with $\ExplSyntaxOn\int_eval:n { \c__fp_max_exponent_int +% + 48 }\ExplSyntaxOff$~digits, then discard the integer part of the +% result, keeping $52$~digits of the fractional part. From the +% fractional part of $x/(2\pi)$ we deduce the octant (quotient of the +% first three digits by~$125$). We then multiply by $8$ or~$-8$ (the +% latter when the octant is odd), ignore any integer part (related to +% the octant), and convert the fractional part to an extended precision +% number, before multiplying by~$\pi/4$ to convert back to a value in +% radians in $[0,\pi/4]$. +% +% It is possible to prove that given the precision of floating points +% and their range of exponents, the $52$~digits may start at most with +% $24$~zeros. The $5$~last digits are affected by carries from +% computations which are not done, hence we are left with at least $52 - +% 24 - 5 = 23$ significant digits, enough to round correctly up to +% $0.6\cdot\text{ulp}$ in all cases. +% +% ^^A todo: if the exponent range is reduced, store 1/2pi as a simple tl +% \begin{macro}[aux, EXP]{\@@_trig_inverse_two_pi:} +% This macro expands to |,,!| or~|,!| followed by $10112$~decimals of +% $10^{-16}/(2\pi)$. The number of decimals we really need is the +% maximum exponent plus the number of digits we will need later,~$52$, +% plus~$12$ ($4-1$~groups of $4$~digits). We store the decimals as a +% control sequence name, and convert it to a token list when required: +% strings take up less memory than their token list representation. +% \begin{macrocode} +\cs_new_nopar:Npx \@@_trig_inverse_two_pi: + { + \exp_not:n { \exp_after:wN \use_none:n \token_to_str:N } + \cs:w , , ! + 0000000000000000159154943091895335768883763372514362034459645740 ~ + 4564487476673440588967976342265350901138027662530859560728427267 ~ + 5795803689291184611457865287796741073169983922923996693740907757 ~ + 3077746396925307688717392896217397661693362390241723629011832380 ~ + 1142226997557159404618900869026739561204894109369378440855287230 ~ + 9994644340024867234773945961089832309678307490616698646280469944 ~ + 8652187881574786566964241038995874139348609983868099199962442875 ~ + 5851711788584311175187671605465475369880097394603647593337680593 ~ + 0249449663530532715677550322032477781639716602294674811959816584 ~ + 0606016803035998133911987498832786654435279755070016240677564388 ~ + 8495713108801221993761476813777647378906330680464579784817613124 ~ + 2731406996077502450029775985708905690279678513152521001631774602 ~ + 0924811606240561456203146484089248459191435211575407556200871526 ~ + 6068022171591407574745827225977462853998751553293908139817724093 ~ + 5825479707332871904069997590765770784934703935898280871734256403 ~ + 6689511662545705943327631268650026122717971153211259950438667945 ~ + 0376255608363171169525975812822494162333431451061235368785631136 ~ + 3669216714206974696012925057833605311960859450983955671870995474 ~ + 6510431623815517580839442979970999505254387566129445883306846050 ~ + 7852915151410404892988506388160776196993073410389995786918905980 ~ + 9373777206187543222718930136625526123878038753888110681406765434 ~ + 0828278526933426799556070790386060352738996245125995749276297023 ~ + 5940955843011648296411855777124057544494570217897697924094903272 ~ + 9477021664960356531815354400384068987471769158876319096650696440 ~ + 4776970687683656778104779795450353395758301881838687937766124814 ~ + 9530599655802190835987510351271290432315804987196868777594656634 ~ + 6221034204440855497850379273869429353661937782928735937843470323 ~ + 0237145837923557118636341929460183182291964165008783079331353497 ~ + 7909974586492902674506098936890945883050337030538054731232158094 ~ + 3197676032283131418980974982243833517435698984750103950068388003 ~ + 9786723599608024002739010874954854787923568261139948903268997427 ~ + 0834961149208289037767847430355045684560836714793084567233270354 ~ + 8539255620208683932409956221175331839402097079357077496549880868 ~ + 6066360968661967037474542102831219251846224834991161149566556037 ~ + 9696761399312829960776082779901007830360023382729879085402387615 ~ + 5744543092601191005433799838904654921248295160707285300522721023 ~ + 6017523313173179759311050328155109373913639645305792607180083617 ~ + 9548767246459804739772924481092009371257869183328958862839904358 ~ + 6866663975673445140950363732719174311388066383072592302759734506 ~ + 0548212778037065337783032170987734966568490800326988506741791464 ~ + 6835082816168533143361607309951498531198197337584442098416559541 ~ + 5225064339431286444038388356150879771645017064706751877456059160 ~ + 8716857857939226234756331711132998655941596890719850688744230057 ~ + 5191977056900382183925622033874235362568083541565172971088117217 ~ + 9593683256488518749974870855311659830610139214454460161488452770 ~ + 2511411070248521739745103866736403872860099674893173561812071174 ~ + 0478899368886556923078485023057057144063638632023685201074100574 ~ + 8592281115721968003978247595300166958522123034641877365043546764 ~ + 6456565971901123084767099309708591283646669191776938791433315566 ~ + 5066981321641521008957117286238426070678451760111345080069947684 ~ + 2235698962488051577598095339708085475059753626564903439445420581 ~ + 7886435683042000315095594743439252544850674914290864751442303321 ~ + 3324569511634945677539394240360905438335528292434220349484366151 ~ + 4663228602477666660495314065734357553014090827988091478669343492 ~ + 2737602634997829957018161964321233140475762897484082891174097478 ~ + 2637899181699939487497715198981872666294601830539583275209236350 ~ + 6853889228468247259972528300766856937583659722919824429747406163 ~ + 8183113958306744348516928597383237392662402434501997809940402189 ~ + 6134834273613676449913827154166063424829363741850612261086132119 ~ + 9863346284709941839942742955915628333990480382117501161211667205 ~ + 1912579303552929241134403116134112495318385926958490443846807849 ~ + 0973982808855297045153053991400988698840883654836652224668624087 ~ + 2540140400911787421220452307533473972538149403884190586842311594 ~ + 6322744339066125162393106283195323883392131534556381511752035108 ~ + 7459558201123754359768155340187407394340363397803881721004531691 ~ + 8295194879591767395417787924352761740724605939160273228287946819 ~ + 3649128949714953432552723591659298072479985806126900733218844526 ~ + 7943350455801952492566306204876616134365339920287545208555344144 ~ + 0990512982727454659118132223284051166615650709837557433729548631 ~ + 2041121716380915606161165732000083306114606181280326258695951602 ~ + 4632166138576614804719932707771316441201594960110632830520759583 ~ + 4850305079095584982982186740289838551383239570208076397550429225 ~ + 9847647071016426974384504309165864528360324933604354657237557916 ~ + 1366324120457809969715663402215880545794313282780055246132088901 ~ + 8742121092448910410052154968097113720754005710963406643135745439 ~ + 9159769435788920793425617783022237011486424925239248728713132021 ~ + 7667360756645598272609574156602343787436291321097485897150713073 ~ + 9104072643541417970572226547980381512759579124002534468048220261 ~ + 7342299001020483062463033796474678190501811830375153802879523433 ~ + 4195502135689770912905614317878792086205744999257897569018492103 ~ + 2420647138519113881475640209760554895793785141404145305151583964 ~ + 2823265406020603311891586570272086250269916393751527887360608114 ~ + 5569484210322407772727421651364234366992716340309405307480652685 ~ + 0930165892136921414312937134106157153714062039784761842650297807 ~ + 8606266969960809184223476335047746719017450451446166382846208240 ~ + 8673595102371302904443779408535034454426334130626307459513830310 ~ + 2293146934466832851766328241515210179422644395718121717021756492 ~ + 1964449396532222187658488244511909401340504432139858628621083179 ~ + 3939608443898019147873897723310286310131486955212620518278063494 ~ + 5711866277825659883100535155231665984394090221806314454521212978 ~ + 9734471488741258268223860236027109981191520568823472398358013366 ~ + 0683786328867928619732367253606685216856320119489780733958419190 ~ + 6659583867852941241871821727987506103946064819585745620060892122 ~ + 8416394373846549589932028481236433466119707324309545859073361878 ~ + 6290631850165106267576851216357588696307451999220010776676830946 ~ + 9814975622682434793671310841210219520899481912444048751171059184 ~ + 4139907889455775184621619041530934543802808938628073237578615267 ~ + 7971143323241969857805637630180884386640607175368321362629671224 ~ + 2609428540110963218262765120117022552929289655594608204938409069 ~ + 0760692003954646191640021567336017909631872891998634341086903200 ~ + 5796637103128612356988817640364252540837098108148351903121318624 ~ + 7228181050845123690190646632235938872454630737272808789830041018 ~ + 9485913673742589418124056729191238003306344998219631580386381054 ~ + 2457893450084553280313511884341007373060595654437362488771292628 ~ + 9807423539074061786905784443105274262641767830058221486462289361 ~ + 9296692992033046693328438158053564864073184440599549689353773183 ~ + 6726613130108623588021288043289344562140479789454233736058506327 ~ + 0439981932635916687341943656783901281912202816229500333012236091 ~ + 8587559201959081224153679499095448881099758919890811581163538891 ~ + 6339402923722049848375224236209100834097566791710084167957022331 ~ + 7897107102928884897013099533995424415335060625843921452433864640 ~ + 3432440657317477553405404481006177612569084746461432976543900008 ~ + 3826521145210162366431119798731902751191441213616962045693602633 ~ + 6102355962140467029012156796418735746835873172331004745963339773 ~ + 2477044918885134415363760091537564267438450166221393719306748706 ~ + 2881595464819775192207710236743289062690709117919412776212245117 ~ + 2354677115640433357720616661564674474627305622913332030953340551 ~ + 3841718194605321501426328000879551813296754972846701883657425342 ~ + 5016994231069156343106626043412205213831587971115075454063290657 ~ + 0248488648697402872037259869281149360627403842332874942332178578 ~ + 7750735571857043787379693402336902911446961448649769719434527467 ~ + 4429603089437192540526658890710662062575509930379976658367936112 ~ + 8137451104971506153783743579555867972129358764463093757203221320 ~ + 2460565661129971310275869112846043251843432691552928458573495971 ~ + 5042565399302112184947232132380516549802909919676815118022483192 ~ + 5127372199792134331067642187484426215985121676396779352982985195 ~ + 8545392106957880586853123277545433229161989053189053725391582222 ~ + 9232597278133427818256064882333760719681014481453198336237910767 ~ + 1255017528826351836492103572587410356573894694875444694018175923 ~ + 0609370828146501857425324969212764624247832210765473750568198834 ~ + 5641035458027261252285503154325039591848918982630498759115406321 ~ + 0354263890012837426155187877318375862355175378506956599570028011 ~ + 5841258870150030170259167463020842412449128392380525772514737141 ~ + 2310230172563968305553583262840383638157686828464330456805994018 ~ + 7001071952092970177990583216417579868116586547147748964716547948 ~ + 8312140431836079844314055731179349677763739898930227765607058530 ~ + 4083747752640947435070395214524701683884070908706147194437225650 ~ + 2823145872995869738316897126851939042297110721350756978037262545 ~ + 8141095038270388987364516284820180468288205829135339013835649144 ~ + 3004015706509887926715417450706686888783438055583501196745862340 ~ + 8059532724727843829259395771584036885940989939255241688378793572 ~ + 7967951654076673927031256418760962190243046993485989199060012977 ~ + 7469214532970421677817261517850653008552559997940209969455431545 ~ + 2745856704403686680428648404512881182309793496962721836492935516 ~ + 2029872469583299481932978335803459023227052612542114437084359584 ~ + 9443383638388317751841160881711251279233374577219339820819005406 ~ + 3292937775306906607415304997682647124407768817248673421685881509 ~ + 9133422075930947173855159340808957124410634720893194912880783576 ~ + 3115829400549708918023366596077070927599010527028150868897828549 ~ + 4340372642729262103487013992868853550062061514343078665396085995 ~ + 0058714939141652065302070085265624074703660736605333805263766757 ~ + 2018839497277047222153633851135483463624619855425993871933367482 ~ + 0422097449956672702505446423243957506869591330193746919142980999 ~ + 3424230550172665212092414559625960554427590951996824313084279693 ~ + 7113207021049823238195747175985519501864630940297594363194450091 ~ + 9150616049228764323192129703446093584259267276386814363309856853 ~ + 2786024332141052330760658841495858718197071242995959226781172796 ~ + 4438853796763139274314227953114500064922126500133268623021550837 + \cs_end: + } +% \end{macrocode} +% \end{macro} % % \begin{macro}[aux, rEXP] % { -% \@@_trig_large:ww, \@@_trig_large:www, -% \@@_trig_large_o:wnnnn, \@@_trig_large_break:w +% \@@_trig_large:ww, +% \@@_trig_large_auxi:wwwwww, +% \@@_trig_large_auxii:ww, +% \@@_trig_large_auxiii:wNNNNNNNN, +% \@@_trig_large_auxiv:wN % } -% 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.3\rfloor$ (the formula chosen always gives a -% non-negative integer, strictly less than the actual ratio by $2\pi$). -% 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 ( #10 - 31416 ) / 62833 ; - {#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 ; +% The exponent~|#1| is between $1$ and~$\ExplSyntaxOn \int_use:N +% \c__fp_max_exponent_int$. We discard the integer part of +% $10^{\text{\texttt{\#1}}-16}/(2\pi)$, that is, the first |#1|~digits +% of $10^{-16}/(2\pi)$, because it yields an integer contribution to +% $x/(2\pi)$. The \texttt{auxii} auxiliary discards~$64$ digits at a +% time thanks to spaces inserted in the result of +% \cs{@@_trig_inverse_two_pi:}, while \texttt{auxiii} discards~$8$ +% digits at a time, and \texttt{auxiv} discards digits one at a time. +% Then $64$~digits are packed into groups of~$4$ and the \texttt{auxv} +% auxiliary is called. +% \begin{macrocode} +\cs_new:Npn \@@_trig_large:ww #1, #2#3#4#5#6; + { + \exp_after:wN \@@_trig_large_auxi:wwwwww + \int_use:N \__int_eval:w (#1 - 32) / 64 \exp_after:wN , + \int_use:N \__int_eval:w (#1 - 4) / 8 \exp_after:wN , + \__int_value:w #1 \@@_trig_inverse_two_pi: ; + {#2}{#3}{#4}{#5} ; + } +\cs_new:Npn \@@_trig_large_auxi:wwwwww #1, #2, #3, #4! + { + \prg_replicate:nn {#1} { \@@_trig_large_auxii:ww } + \prg_replicate:nn { #2 - #1 * \c_eight } + { \@@_trig_large_auxiii:wNNNNNNNN } + \prg_replicate:nn { #3 - #2 * \c_eight } + { \@@_trig_large_auxiv:wN } + \prg_replicate:nn { \c_eight } { \@@_pack_twice_four:wNNNNNNNN } + \@@_trig_large_auxv:www + ; } -\cs_new:Npn \@@_trig_large_break:w \fi: #1; #2; - { \fi: \@@_trig_octant_loop:nnnnnw #2 {0000} {0000} ; } +\cs_new:Npn \@@_trig_large_auxii:ww #1; #2 ~ { #1; } +\cs_new:Npn \@@_trig_large_auxiii:wNNNNNNNN + #1; #2#3#4#5#6#7#8#9 { #1; } +\cs_new:Npn \@@_trig_large_auxiv:wN #1; #2 { #1; } % \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 +% { +% \@@_trig_large_auxv:www, +% \@@_trig_large_auxvi:wnnnnnnnn, +% \@@_trig_large_pack:NNNNNw +% } +% First come the first $64$~digits of the fractional part of +% $10^{\text{\texttt{\#1}}-16}/(2\pi)$, arranged in $16$~blocks +% of~$4$, and ending with a semicolon. Then some more digits of the +% same fractional part, ending with a semicolon, then $4$~blocks of +% $4$~digits holding the significand of the original argument. +% Multiply the $16$-digit significand with the $64$-digit fractional +% part: the \texttt{auxvi} auxiliary receives the significand +% as~|#2#3#4#5| and $16$~digits of the fractional part as~|#6#7#8#9|, +% and computes one step of the usual ladder of \texttt{pack} functions +% we use for multiplication (see \emph{e.g.,} \cs{@@_fixed_mul:wwn}), +% then discards one block of the fractional part to set things up for +% the next step of the ladder. We perform $13$~such steps, replacing +% the last \texttt{middle} shift by the appropriate \texttt{trailing} +% shift, then discard the significand and remaining $3$~blocks from +% the fractional part, as there are not enough digits to compute any +% more step in the ladder. The last semicolon closes the ladder, and +% we return control to the \texttt{auxvii} auxiliary. +% \begin{macrocode} +\cs_new:Npn \@@_trig_large_auxv:www #1; #2; #3; + { + \exp_after:wN \@@_use_i_until_s:nw + \exp_after:wN \@@_trig_large_auxvii:w + \int_use:N \__int_eval:w \c_@@_leading_shift_int + \prg_replicate:nn { \c_thirteen } + { \@@_trig_large_auxvi:wnnnnnnnn } + + \c_@@_trailing_shift_int - \c_@@_middle_shift_int + \@@_use_i_until_s:nw + ; #3 #1 ; ; } -\cs_new:Npn \@@_trig_octant_break:w #1 \fi: + #2#3 #4#5; #6; #7; +\cs_new:Npn \@@_trig_large_auxvi:wnnnnnnnn #1; #2#3#4#5#6#7#8#9 { + \exp_after:wN \@@_trig_large_pack:NNNNNw + \int_use:N \__int_eval:w \c_@@_middle_shift_int + + #2*#9 + #3*#8 + #4*#7 + #5*#6 + #1; {#2}{#3}{#4}{#5} {#7}{#8}{#9} + } +\cs_new:Npn \@@_trig_large_pack:NNNNNw #1#2#3#4#5#6; + { + #1#2#3#4#5 ; #6 } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[aux, rEXP] +% { +% \@@_trig_large_auxvii:w, +% \@@_trig_large_auxviii:w, +% } +% \begin{macro}[aux, EXP] +% { +% \@@_trig_large_auxix:Nw, +% \@@_trig_large_auxx:wNNNNN, +% \@@_trig_large_auxxi:w +% } +% The \texttt{auxvii} auxiliary is followed by $52$~digits and a +% semicolon. We find the octant as the integer part of $8$~times what +% follows, or equivalently as the integer part of $|#1#2#3|/125$, and +% add it to the surrounding integer expression for the octant. We +% then compute $8$~times the $52$-digit number, with a minus sign if +% the octant is odd. Again, the last \texttt{middle} shift is +% converted to a \texttt{trailing} shift. Any integer part (including +% negative values which come up when the octant is odd) is discarded +% by \cs{@@_use_i_until_s:nw}. The resulting fractional part should +% then be converted to radians by multiplying by~$2\pi/8$, but first, +% build an extended precision number by abusing +% \cs{@@_ep_to_ep_loop:N} with the appropriate trailing markers. +% Finally, \cs{@@_trig_small:ww} sets up the argument for the +% functions which compute the Taylor series. +% \begin{macrocode} +\cs_new:Npn \@@_trig_large_auxvii:w #1#2#3 + { + \exp_after:wN \@@_trig_large_auxviii:ww + \int_use:N \__int_eval:w (#1#2#3 - 62) / 125 ; + #1#2#3 + } +\cs_new:Npn \@@_trig_large_auxviii:ww #1; + { + + #1 + \if_int_odd:w #1 \exp_stop_f: + \exp_after:wN \@@_trig_large_auxix:Nw + \exp_after:wN - + \else: + \exp_after:wN \@@_trig_large_auxix:Nw + \exp_after:wN + \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 ; . ; + } +\cs_new_nopar:Npn \@@_trig_large_auxix:Nw + { + \exp_after:wN \@@_use_i_until_s:nw + \exp_after:wN \@@_trig_large_auxxi:w + \int_use:N \__int_eval:w \c_@@_leading_shift_int + \prg_replicate:nn { \c_thirteen } + { \@@_trig_large_auxx:wNNNNN } + + \c_@@_trailing_shift_int - \c_@@_middle_shift_int + ; + } +\cs_new:Npn \@@_trig_large_auxx:wNNNNN #1; #2 #3#4#5#6 + { + \exp_after:wN \@@_trig_large_pack:NNNNNw + \int_use:N \__int_eval:w \c_@@_middle_shift_int + #2 \c_eight * #3#4#5#6 + #1; #2 + } +\cs_new:Npn \@@_trig_large_auxxi:w #1; + { + \exp_after:wN \@@_ep_mul_raw:wwwwN + \int_use:N \__int_eval:w \c_zero \@@_ep_to_ep_loop:N #1 ; ; ! + 0,{7853}{9816}{3397}{4483}{0961}{5661}; + \@@_trig_small:ww } % \end{macrocode} % \end{macro} +% \end{macro} % -% \subsection{Computing the power series} +% \subsubsection{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 +% \begin{macro}[aux, EXP] +% {\@@_sin_series_o:NNwwww, \@@_sin_series_aux_o:NNnwww} +% Here we receive a conversion function \cs{@@_ep_to_float:wwN} or +% \cs{@@_ep_inv_to_float:wwN}, 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: +% point} number delimited by a semicolon, and an extended-precision +% number. 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. +% \item the conversion function~|#1|; +% \item the final sign, which depends on the octant~|#3| and the +% sign~|#2|; +% \item the octant~|#3|, which will control the series we use; +% \item the square |#4 * #4| of the argument as a fixed point number, +% computed with \cs{@@_fixed_mul:wwn}; +% \item the number itself as an extended-precision number. % \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 @@ -488,15 +836,15 @@ % \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. +% is used. Finally, the extended-precision 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; +\cs_new:Npn \@@_sin_series_o:NNwwww #1#2#3. #4; { \@@_fixed_mul:wwn #4; #4; { - \exp_after:wN \@@_sin_series_aux:NNnww + \exp_after:wN \@@_sin_series_aux_o:NNnwww \exp_after:wN #1 \__int_value:w \if_int_odd:w \__int_eval:w ( #3 + \c_two ) / \c_four \__int_eval_end: @@ -506,9 +854,8 @@ \fi: {#3} } - #4 ; } -\cs_new:Npn \@@_sin_series_aux:NNnww #1#2#3 #4; #5; +\cs_new:Npn \@@_sin_series_aux_o:NNnwww #1#2#3 #4; #5,#6; { \if_int_odd:w \__int_eval:w #3 / \c_two \__int_eval_end: \exp_after:wN \use_i:nn @@ -526,6 +873,7 @@ \@@_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}; + { \@@_fixed_continue:wn 0, } } { % 1/17! \@@_fixed_mul_sub_back:wwwn {0000}{0000}{0000}{0028}{1145}{7254}; @@ -537,7 +885,7 @@ \@@_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; + { \@@_ep_mul:wwwwn 0, } #5,#6; } { \exp_after:wN \@@_sanitize:Nw @@ -549,9 +897,11 @@ % \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 +% \begin{macro}[aux, EXP] +% {\@@_tan_series_o:NNwwww, \@@_tan_series_aux_o:Nnwww} +% Contrarily to \cs{@@_sin_series_o:NNwwww} which received a +% 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 @@ -564,25 +914,26 @@ % 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 +% (reduced) input, and the (reduced) input (an extended-precision +% number) 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 +% The ratio is computed by \cs{@@_ep_div:wwwwn}, then converted to a +% floating point number. For octants~|#3| (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; +\cs_new:Npn \@@_tan_series_o:NNwwww #1#2#3. #4; { \@@_fixed_mul:wwn #4; #4; { - \exp_after:wN \@@_tan_series_aux_o:Nnww + \exp_after:wN \@@_tan_series_aux_o:Nnwww \__int_value:w \if_int_odd:w \__int_eval:w #3 / \c_two \__int_eval_end: \exp_after:wN \reverse_if:N @@ -590,9 +941,8 @@ \if_meaning:w #1#2 2 \else: 0 \fi: {#3} } - #4 ; } -\cs_new:Npn \@@_tan_series_aux_o:Nnww #1 #2 #3; #4; +\cs_new:Npn \@@_tan_series_aux_o:Nnwww #1 #2 #3; #4,#5; { \@@_fixed_mul_sub_back:wwwn {0000}{0000}{1527}{3493}{0856}{7059}; #3; {0000}{0159}{6080}{0274}{5257}{6472}; @@ -600,24 +950,615 @@ \@@_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; + { \@@_ep_mul:wwwwn 0, } #4,#5; + { + \@@_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}; + { + \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: + \@@_ep_div:wwwwn 0, + } + } + { + \exp_after:wN \@@_sanitize:Nw + \exp_after:wN #1 + \int_use:N \__int_eval:w \@@_ep_to_float:wwN + } + #1 + } +% \end{macrocode} +% \end{macro} +% +% \subsection{Inverse trigonometric functions} +% +% \providecommand*{\atan}{\operatorname{atan}} +% +% All inverse trigonometric functions (arcsine, arccosine, arctangent, +% arccotangent, arccosecant, and arcsecant) are based on a function +% often denoted \texttt{atan2}. This function is accessed directly by +% feeding two arguments to arctangent, and is defined by \(\atan(y, x) = +% \atan(y/x)\) for generic \(y\) and~\(x\). Its advantages over the +% conventional arctangent is that it takes values in $[-\pi,\pi]$ rather +% than $[-\pi/2,\pi/2]$, and that it is better behaved in boundary +% cases. Other inverse trigonometric functions are expressed in terms +% of \(\atan\) as +% \begin{align} +% \operatorname{acos} x & = \atan(\sqrt{1-x^2}, x) \\ +% \operatorname{asin} x & = \atan(x, \sqrt{1-x^2}) \\ +% \operatorname{asec} x & = \atan(\sqrt{x^2-1}, 1) \\ +% \operatorname{acsc} x & = \atan(1, \sqrt{x^2-1}) \\ +% \operatorname{atan} x & = \atan(x, 1) \\ +% \operatorname{acot} x & = \atan(1, x) . +% \end{align} +% Rather than introducing a new function, \texttt{atan2}, the arctangent +% function \texttt{atan} is overloaded: it can take one or two +% arguments. In the comments below, following many texts, we call the +% first argument~$y$ and the second~$x$, because $\atan(y, x) = \atan(y +% / x)$ is the angular coordinate of the point $(x, y)$. +% +% As for direct trigonometric functions, the first step in computing +% $\atan(y, x)$ is argument reduction. The sign of~$y$ will give that +% of the result. We distinguish eight regions where the point $(x, +% \lvert y\rvert)$ can lie, of angular size roughly $\pi/8$, +% characterized by their ``octant'', between $0$ and~$7$ included. In +% each region, we compute an arctangent as a Taylor series, then shift +% this arctangent by the appropriate multiple of $\pi/4$ and sign to get +% the result. Here is a list of octants, and how we compute the +% arctangent (we assume $y>0$: otherwise replace $y$ by~$-y$ below): +% \begin{itemize} +% \item[0] $0 < \lvert y\rvert < 0.41421 x$, then +% $\atan\frac{\lvert y\rvert}{x}$ +% is given by a nicely convergent Taylor series; +% \item[1] $0 < 0.41421 x < \lvert y\rvert < x$, then +% $\atan\frac{\lvert y\rvert}{x} +% = \frac{\pi}{4}-\atan\frac{x-\lvert y\rvert}{x+\lvert y\rvert}$; +% \item[2] $0 < 0.41421 \lvert y\rvert < x < \lvert y\rvert$, then +% $\atan\frac{\lvert y\rvert}{x} +% = \frac{\pi}{4}+\atan\frac{-x+\lvert y\rvert}{x+\lvert y\rvert}$; +% \item[3] $0 < x < 0.41421 \lvert y\rvert$, then +% $\atan\frac{\lvert y\rvert}{x} +% = \frac{\pi}{2}-\atan\frac{x}{\lvert y\rvert}$; +% \item[4] $0 < -x < 0.41421 \lvert y\rvert$, then +% $\atan\frac{\lvert y\rvert}{x} +% = \frac{\pi}{2}+\atan\frac{-x}{\lvert y\rvert}$; +% \item[5] $0 < 0.41421 \lvert y\rvert < -x < \lvert y\rvert$, then +% $\atan\frac{\lvert y\rvert}{x} +% =\frac{3\pi}{4}-\atan\frac{x+\lvert y\rvert}{-x+\lvert y\rvert}$; +% \item[6] $0 < -0.41421 x < \lvert y\rvert < -x$, then +% $\atan\frac{\lvert y\rvert}{x} +% =\frac{3\pi}{4}+\atan\frac{-x-\lvert y\rvert}{-x+\lvert y\rvert}$; +% \item[7] $0 < \lvert y\rvert < -0.41421 x$, then +% $\atan\frac{\lvert y\rvert}{x} +% = \pi-\atan\frac{\lvert y\rvert}{-x}$. +% \end{itemize} +% In the following, we will denote by~$z$ the ratio among +% $\lvert\frac{y}{x}\rvert$, $\lvert\frac{x}{y}\rvert$, +% $\lvert\frac{x+y}{x-y}\rvert$, $\lvert\frac{x-y}{x+y}\rvert$ which +% appears in the right-hand side above. +% +% \subsubsection{Arctangent and arccotangent} +% +% \begin{macro}[int, EXP]{\@@_atan_o:Nw, \@@_acot_o:Nw} +% \begin{macro}[aux, EXP]{\@@_atan_dispatch_o:NNnNw} +% The parsing step manipulates \texttt{atan} and \texttt{acot} like +% \texttt{min} and \texttt{max}, reading in an array of operands, but +% also leaves \cs{use_i:nn} or \cs{use_ii:nn} depending on whether the +% result should be given in radians or in degrees. Here, we dispatch +% according to the number of arguments. The one-argument versions of +% arctangent and arccotangent are special cases of the two-argument +% ones: $\atan(y) = \atan(y, 1) = \operatorname{acot}(1, y)$ and +% $\operatorname{acot}(x) = \atan(1, x) = \operatorname{acot}(x, 1)$. +% \begin{macrocode} +\cs_new_nopar:Npn \@@_atan_o:Nw + { + \@@_atan_dispatch_o:NNnNw + \@@_acotii_o:Nww \@@_atanii_o:Nww { atan } + } +\cs_new_nopar:Npn \@@_acot_o:Nw + { + \@@_atan_dispatch_o:NNnNw + \@@_atanii_o:Nww \@@_acotii_o:Nww { acot } + } +\cs_new:Npn \@@_atan_dispatch_o:NNnNw #1#2#3#4#5@ + { + \if_case:w + \__int_eval:w \@@_array_count:n {#5} - \c_one \__int_eval_end: + \exp_after:wN #1 \exp_after:wN #4 \c_one_fp #5 + \tex_romannumeral:D + \or: #2 #4 #5 \tex_romannumeral:D + \else: + \__msg_kernel_expandable_error:nnnnn + { kernel } { fp-num-args } { #3() } { 1 } { 2 } + \exp_after:wN \c_nan_fp \tex_romannumeral:D + \fi: + \exp_after:wN \c_zero + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}[int, EXP]{\@@_atanii_o:Nww, \@@_acotii_o:Nww} +% If either operand is \texttt{nan}, we return it. If both are +% normal, we call \cs{@@_atan_normal_o:NNnwNnw}. If both are zero or +% both infinity, we call \cs{@@_atan_inf_o:NNNw} with argument~$2$, +% leading to a result among $\{\pm\pi/4, \pm 3\pi/4\}$ (in degrees, +% $\{\pm 45, \pm 135\}$). Otherwise, one is much bigger than the +% other, and we call \cs{@@_atan_inf_o:NNNw} with either an argument +% of~$4$, leading to the values $\pm\pi/2$ (in degrees,~$\pm 90$), +% or~$0$, leading to $\{\pm 0, \pm\pi\}$ (in degrees, $\{\pm 0,\pm +% 180\}$). Since $\operatorname{acot}(x, y) = \atan(y, x)$, +% \cs{@@_acotii_o:ww} simply reverses its two arguments. +% \begin{macrocode} +\cs_new:Npn \@@_atanii_o:Nww + #1 \s_@@ \@@_chk:w #2#3#4; \s_@@ \@@_chk:w #5 + { + \if_meaning:w 3 #2 \@@_case_return_i_o:ww \fi: + \if_meaning:w 3 #5 \@@_case_return_ii_o:ww \fi: + \if_case:w + \if_meaning:w #2 #5 + \if_meaning:w 1 #2 \c_ten \else: \c_zero \fi: + \else: + \if_int_compare:w #2 > #5 \c_one \else: \c_two \fi: + \fi: + \@@_case_return:nw { \@@_atan_inf_o:NNNw #1 #3 \c_two } + \or: \@@_case_return:nw { \@@_atan_inf_o:NNNw #1 #3 \c_four } + \or: \@@_case_return:nw { \@@_atan_inf_o:NNNw #1 #3 \c_zero } + \fi: + \@@_atan_normal_o:NNnwNnw #1 + \s_@@ \@@_chk:w #2#3#4; + \s_@@ \@@_chk:w #5 + } +\cs_new:Npn \@@_acotii_o:Nww #1#2; #3; + { \@@_atanii_o:Nww #1#3; #2; } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[aux, EXP]{\@@_atan_inf_o:NNNw} +% This auxiliary is called whenever one number is $\pm 0$ or +% $\pm\infty$ (and neither is \nan{}). Then the result only depends +% on the signs, and its value is a multiple of $\pi/4$. We use the +% same auxiliary as for normal numbers, +% \cs{@@_atan_combine_o:NwwwwwN}, with arguments the final sign~|#2|; +% the octant~|#3|; $\atan z/z=1$ as a fixed point number; $z=0$~as a +% fixed point number; and $z=0$~as an extended-precision number. +% Given the values we provide, $\atan z$ will be computed to be~$0$, +% and the result will be $[|#3|/2]\cdot\pi/4$ if the sign~|#5| of~$x$ +% is positive, and $[(7-|#3|)/2]\cdot\pi/4$ for negative~$x$, where +% the divisions are rounded up. +% \begin{macrocode} +\cs_new:Npn \@@_atan_inf_o:NNNw #1#2#3 \s_@@ \@@_chk:w #4#5#6; + { + \exp_after:wN \@@_atan_combine_o:NwwwwwN + \exp_after:wN #2 + \int_use:N \__int_eval:w + \if_meaning:w 2 #5 \c_seven - \fi: #3 \exp_after:wN ; + \c_@@_one_fixed_tl ; + {0000}{0000}{0000}{0000}{0000}{0000}; + 0,{0000}{0000}{0000}{0000}{0000}{0000}; #1 + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[aux, EXP]{\@@_atan_normal_o:NNnwNnw} +% Here we simply reorder the floating point data into a pair of signed +% extended-precision numbers, that is, a sign, an exponent ending with +% a comma, and a six-block mantissa ending with a semi-colon. This +% extended precision is required by other inverse trigonometric +% functions, to compute things like $\atan(x,\sqrt{1-x^2})$ without +% intermediate rounding errors. +% \begin{macrocode} +\cs_new_protected:Npn \@@_atan_normal_o:NNnwNnw + #1 \s_@@ \@@_chk:w 1#2#3#4; \s_@@ \@@_chk:w 1#5#6#7; + { + \@@_atan_test_o:NwwNwwN + #2 #3, #4{0000}{0000}; + #5 #6, #7{0000}{0000}; #1 + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[aux, EXP]{\@@_atan_test_o:NwwNwwN} +% This receives: the sign~|#1| of~$y$, its exponent~|#2|, its $24$ +% digits~|#3| in groups of~$4$, and similarly for~$x$. We prepare to +% call \cs{@@_atan_combine_o:NwwwwwN} which expects the sign~|#1|, the +% octant, the ratio $(\atan z)/z = 1 - \cdots$, and the value of~$z$, +% both as a fixed point number and as an extended-precision floating +% point number with a mantissa in $[0.01,1)$. For now, we place |#1| +% as a first argument, and start an integer expression for the octant. +% The sign of $x$ does not affect what~$z$ will be, so we simply leave +% a contribution to the octant: $\meta{octant} \to 7 - \meta{octant}$ +% for negative~$x$. Then we order $\lvert y\rvert$ and $\lvert +% x\rvert$ in a non-decreasing order: if $\lvert y\rvert > \lvert +% x\rvert$, insert $3-$ in the expression for the octant, and swap the +% two numbers. The finer test with $0.41421$ is done by +% \cs{@@_atan_div:wnwwnw} after the operands have been ordered. +% \begin{macrocode} +\cs_new:Npn \@@_atan_test_o:NwwNwwN #1#2,#3; #4#5,#6; + { + \exp_after:wN \@@_atan_combine_o:NwwwwwN + \exp_after:wN #1 + \int_use:N \__int_eval:w + \if_meaning:w 2 #4 + \c_seven - \__int_eval:w + \fi: + \if_int_compare:w + \@@_ep_compare:wwww #2,#3; #5,#6; > \c_zero + \c_three - + \exp_after:wN \@@_reverse_args:Nww + \fi: + \@@_atan_div:wnwwnw #2,#3; #5,#6; + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[aux, rEXP]{\@@_atan_div:wnwwnw, \@@_atan_near:wwwn} +% \begin{macro}[aux, EXP]{\@@_atan_near_aux:wwn} +% This receives two positive numbers $a$ and~$b$ (equal to $\lvert +% x\rvert$ and~$\lvert y\rvert$ in some order), each as an exponent +% and $6$~blocks of $4$~digits, such that $0<a<b$. If $0.41421b<a$, +% the two numbers are ``near'', hence the point $(y,x)$ that we +% started with is closer to the diagonals $\{\lvert y\rvert = \lvert +% x\rvert\}$ than to the axes $\{xy = 0\}$. In that case, the octant +% is~$1$ (possibly combined with the $7-$ and $3-$ inserted earlier) +% and we wish to compute $\atan\frac{b-a}{a+b}$. Otherwise, the +% octant is~$0$ (again, combined with earlier terms) and we wish to +% compute $\atan\frac{a}{b}$. In any case, call \cs{@@_atan_auxi:ww} +% followed by~$z$, as a comma-delimited exponent and a fixed point +% number. +% \begin{macrocode} +\cs_new:Npn \@@_atan_div:wnwwnw #1,#2#3; #4,#5#6; + { + \if_int_compare:w + \__int_eval:w 41421 * #5 < #2 000 + \if_case:w \__int_eval:w #4 - #1 \__int_eval_end: 00 \or: 0 \fi: + \exp_stop_f: + \exp_after:wN \@@_atan_near:wwwn + \fi: + \c_zero + \@@_ep_div:wwwwn #1,{#2}#3; #4,{#5}#6; + \@@_atan_auxi:ww + } +\cs_new:Npn \@@_atan_near:wwwn + \c_zero \@@_ep_div:wwwwn #1,#2; #3, + { + \c_one + \@@_ep_to_fixed:wwn #1 - #3, #2; + \@@_atan_near_aux:wwn + } +\cs_new:Npn \@@_atan_near_aux:wwn #1; #2; + { + \@@_fixed_add:wwn #1; #2; + { \@@_fixed_sub:wwn #2; #1; { \@@_ep_div:wwwwn 0, } 0, } + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}[aux, EXP]{\@@_atan_auxi:ww, \@@_atan_auxii:w} +% Convert~$z$ from a representation as an exponent and a fixed point +% number in $[0.01,1)$ to a fixed point number only, then set up the +% call to \cs{@@_atan_Taylor_loop:www}, followed by the fixed point +% representation of~$z$ and the old representation. +% \begin{macrocode} +\cs_new:Npn \@@_atan_auxi:ww #1,#2; + { \@@_ep_to_fixed:wwn #1,#2; \@@_atan_auxii:w #1,#2; } +\cs_new:Npn \@@_atan_auxii:w #1; + { + \@@_fixed_mul:wwn #1; #1; + { + \@@_atan_Taylor_loop:www 39 ; + {0000}{0000}{0000}{0000}{0000}{0000} ; + } + ! #1; + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[aux, EXP] +% {\@@_atan_Taylor_loop:www, \@@_atan_Taylor_break:w} +% We compute the series of $(\atan z)/z$. A typical intermediate +% stage has $|#1|=2k-1$, $|#2| = +% \frac{1}{2k+1}-z^2(\frac{1}{2k+3}-z^2(\cdots-z^2\frac{1}{39}))$, and +% $|#3|=z^2$. To go to the next step $k\to k-1$, we compute +% $\frac{1}{2k-1}$, then subtract from it $z^2$ times |#2|. The loop +% stops when $k=0$: then |#2| is $(\atan z)/z$, and there is a need to +% clean up all the unnecessary data, end the integer expression +% computing the octant with a semicolon, and leave the result~|#2| +% afterwards. +% \begin{macrocode} +\cs_new:Npn \@@_atan_Taylor_loop:www #1; #2; #3; + { + \if_int_compare:w #1 = \c_minus_one + \@@_atan_Taylor_break:w + \fi: + \exp_after:wN \@@_fixed_div_int:wwN \c_@@_one_fixed_tl ; #1; + \@@_rrot:www \@@_fixed_mul_sub_back:wwwn #2; #3; + { + \exp_after:wN \@@_atan_Taylor_loop:www + \int_use:N \__int_eval:w #1 - \c_two ; + } + #3; + } +\cs_new:Npn \@@_atan_Taylor_break:w + \fi: #1 \@@_fixed_mul_sub_back:wwwn #2; #3 ! + { \fi: ; #2 ; } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[aux, EXP] +% {\@@_atan_combine_o:NwwwwwN, \@@_atan_combine_aux:ww} +% This receives a \meta{sign}, an \meta{octant}, a fixed point value +% of $(\atan z)/z$, a fixed point number~$z$, and another +% representation of~$z$, as an \meta{exponent} and the fixed point +% number $10^{-\meta{exponent}} z$, followed by either \cs{use_i:nn} +% (when working in radians) or \cs{use_ii:nn} (when working in +% degrees). The function computes the floating point result +% \begin{equation} +% \meta{sign} \left( +% \left\lceil\frac{\meta{octant}}{2}\right\rceil +% \frac{\pi}{4} +% + (-1)^{\meta{octant}} \frac{\atan z}{z} \cdot z\right) \,, +% \end{equation} +% multiplied by $180/\pi$ if working in degrees, and using in any case +% the most appropriate representation of~$z$. The floating point +% result is passed to \cs{@@_sanitize:Nw}, which checks for overflow +% or underflow. If the octant is~$0$, leave the exponent~|#5| for +% \cs{@@_sanitize:Nw}, and multiply $|#3|=\frac{\atan z}{z}$ +% with~|#6|, the adjusted~$z$. Otherwise, multiply $|#3|=\frac{\atan +% z}{z}$ with $|#4|=z$, then compute the appropriate multiple of +% $\frac{\pi}{4}$ and add or subtract the product $|#3|\cdot|#4|$. In +% both cases, convert to a floating point with +% \cs{@@_fixed_to_float:wN}. +% \begin{macrocode} +\cs_new:Npn \@@_atan_combine_o:NwwwwwN #1 #2; #3; #4; #5,#6; #7 + { + \exp_after:wN \@@_sanitize:Nw + \exp_after:wN #1 + \int_use:N \__int_eval:w + \if_meaning:w 0 #2 + \exp_after:wN \use_i:nn + \else: + \exp_after:wN \use_ii:nn + \fi: + { #5 \@@_fixed_mul:wwn #3; #6; } + { + \@@_fixed_mul:wwn #3; #4; + { + \exp_after:wN \@@_atan_combine_aux:ww + \int_use:N \__int_eval:w #2 / \c_two ; #2; + } + } + { #7 \@@_fixed_to_float:wN \@@_fixed_to_float_rad:wN } + #1 + } +\cs_new:Npn \@@_atan_combine_aux:ww #1; #2; + { + \@@_fixed_mul_short:wwn + {7853}{9816}{3397}{4483}{0961}{5661}; + {#1}{0000}{0000}; + { + \if_int_odd:w #2 \exp_stop_f: + \exp_after:wN \@@_fixed_sub:wwn + \else: + \exp_after:wN \@@_fixed_add:wwn + \fi: + } + } +% \end{macrocode} +% \end{macro} +% +% \subsubsection{Arcsine and arccosine} +% +% \begin{macro}[int, EXP]{\@@_asin_o:w} +% Again, the first argument provided by \pkg{l3fp-parse} is +% \cs{use_i:nn} if we are to work in radians and \cs{use_ii:nn} for +% degrees. Then comes a floating point number. The arcsine of $\pm +% 0$ or \nan{} is the same floating point number. The arcsine of +% $\pm\infty$ raises an invalid operation exception. Otherwise, call +% an auxiliary common with \cs{@@_acos_o:w}, feeding it information +% about what function is being performed (for ``invalid operation'' +% exceptions). +% \begin{macrocode} +\cs_new:Npn \@@_asin_o:w #1 \s_@@ \@@_chk:w #2#3; @ + { + \if_case:w #2 \exp_stop_f: + \@@_case_return_same_o:w + \or: + \@@_case_use:nw + { \@@_asin_normal_o:NfwNnnnnw #1 { #1 { asin } { asind } } } + \or: + \@@_case_use:nw + { \@@_invalid_operation_o:fw { #1 { asin } { asind } } } + \else: + \@@_case_return_same_o:w + \fi: + \s_@@ \@@_chk:w #2 #3; + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[int, EXP]{\@@_acos_o:w} +% The arccosine of $\pm 0$ is $\pi / 2$ (in degrees,~$90$). The +% arccosine of $\pm\infty$ raises an invalid operation exception. The +% arccosine of \nan{} is itself. Otherwise, call an auxiliary common +% with \cs{@@_sin_o:w}, informing it that it was called by +% \texttt{acos} or \texttt{acosd}, and preparing to swap some +% arguments down the line. +% \begin{macrocode} +\cs_new:Npn \@@_acos_o:w #1 \s_@@ \@@_chk:w #2#3; @ + { + \if_case:w #2 \exp_stop_f: + \@@_case_use:nw { \@@_atan_inf_o:NNNw #1 0 \c_four } + \or: + \@@_case_use:nw + { + \@@_asin_normal_o:NfwNnnnnw #1 { #1 { acos } { acosd } } + \@@_reverse_args:Nww + } + \or: + \@@_case_use:nw + { \@@_invalid_operation_o:fw { #1 { acos } { acosd } } } + \else: + \@@_case_return_same_o:w + \fi: + \s_@@ \@@_chk:w #2 #3; + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[aux, EXP]{\@@_asin_normal_o:NfwNnnnnw} +% If the exponent~|#5| is strictly less than~$1$, the operand lies +% within $(-1,1)$ and the operation is permitted: call +% \cs{@@_asin_auxi_o:nNww} with the appropriate arguments. If the +% number is exactly~$\pm 1$ (the test works because we know that +% $|#5|\geq 1$, $|#6#7|\geq 10000000$, $|#8#9|\geq 0$, with equality +% only for $\pm 1$), we also call \cs{@@_asin_auxi_o:nNww}. +% Otherwise, \cs{@@_use_i:ww} gets rid of the \texttt{asin} auxiliary, +% and raises instead an invalid operation, because the operand is +% outside the domain of arcsine or arccosine. +% \begin{macrocode} +\cs_new:Npn \@@_asin_normal_o:NfwNnnnnw + #1#2#3 \s_@@ \@@_chk:w 1#4#5#6#7#8#9; + { + \if_int_compare:w #5 < \c_one + \exp_after:wN \@@_use_none_until_s:w + \fi: + \if_int_compare:w \__int_eval:w #5 + #6#7 + #8#9 = 1000 0001 ~ + \exp_after:wN \@@_use_none_until_s:w + \fi: + \@@_use_i:ww + \@@_invalid_operation_o:fw {#2} + \s_@@ \@@_chk:w 1#4{#5}{#6}{#7}{#8}{#9}; + \@@_asin_auxi_o:NnNww + #1 {#3} #4 #5,{#6}{#7}{#8}{#9}{0000}{0000}; + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[aux, EXP]{\@@_asin_auxi_o:NnNww, \@@_asin_isqrt:wn} +% We compute $x/\sqrt{1-x^2}$. This function is used by \texttt{asin} +% and \texttt{acos}, but also by \texttt{acsc} and \texttt{asec} after +% inverting the operand, thus it must manipulate extended-precision +% numbers. First evaluate $1-x^2$ as $(1+x)(1-x)$: this behaves +% better near~$x=1$. We do the addition/subtraction with fixed point +% numbers (they are not implemented for extended-precision floats), +% but go back to extended-precision floats to multiply and compute the +% inverse square root $1/\sqrt{1-x^2}$. Finally, multiply by the +% (positive) extended-precision float $\lvert x\rvert$, and feed the +% (signed) result, and the number~$+1$, as arguments to the arctangent +% function. When computing the arccosine, the arguments +% $x/\sqrt{1-x^2}$ and~$+1$ are swapped by~|#2| +% (\cs{@@_reverse_args:Nww} in that case) before +% \cs{@@_atan_test_o:NwwNwwN} is evaluated. Note that the arctangent +% function requires normalized arguments, hence the need for +% \texttt{ep_to_ep} and \texttt{continue} after \texttt{ep_mul}. +% \begin{macrocode} +\cs_new:Npn \@@_asin_auxi_o:NnNww #1#2#3#4,#5; + { + \@@_ep_to_fixed:wwn #4,#5; + \@@_asin_isqrt:wn + \@@_ep_mul:wwwwn #4,#5; + \@@_ep_to_ep:wwN + \@@_fixed_continue:wn + { #2 \@@_atan_test_o:NwwNwwN #3 } + 0 1,{1000}{0000}{0000}{0000}{0000}{0000}; #1 + } +\cs_new:Npn \@@_asin_isqrt:wn #1; + { + \exp_after:wN \@@_fixed_sub:wwn \c_@@_one_fixed_tl ; #1; + { + \@@_fixed_add_one:wN #1; + \@@_fixed_continue:wn { \@@_ep_mul:wwwwn 0, } 0, + } + \@@_ep_isqrt:wwn + } +% \end{macrocode} +% \end{macro} +% +% \subsubsection{Arccosecant and arcsecant} +% +% \begin{macro}[int, EXP]{\@@_acsc_o:w} +% Cases are mostly labelled by~|#2|, except when |#2| is~$2$: then we +% use |#3#2|, which is $02=2$ when the number is $+\infty$ and +% $22$~when the number is $-\infty$. The arccosecant of $\pm 0$ +% raises an invalid operation exception. The arccosecant of +% $\pm\infty$ is $\pm 0$ with the same sign. The arcosecant of \nan{} +% is itself. Otherwise, \cs{@@_acsc_normal_o:NfwNnw} does some more +% tests, keeping the function name (\texttt{acsc} or \texttt{acscd}) +% as an argument for invalid operation exceptions. +% \begin{macrocode} +\cs_new:Npn \@@_acsc_o:w #1 \s_@@ \@@_chk:w #2#3#4; @ + { + \if_case:w \if_meaning:w 2 #2 #3 \fi: #2 \exp_stop_f: + \@@_case_use:nw + { \@@_invalid_operation_o:fw { #1 { acsc } { acscd } } } + \or: \@@_case_use:nw + { \@@_acsc_normal_o:NfwNnw #1 { #1 { acsc } { acscd } } } + \or: \@@_case_return_o:Nw \c_zero_fp + \or: \@@_case_return_same_o:w + \else: \@@_case_return_o:Nw \c_minus_zero_fp + \fi: + \s_@@ \@@_chk:w #2 #3 #4; + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[int, EXP]{\@@_asec_o:w} +% The arcsecant of $\pm 0$ raises an invalid operation exception. The +% arcsecant of $\pm\infty$ is $\pi / 2$ (in degrees,~$90$). The +% arcosecant of \nan{} is itself. Otherwise, do some more tests, +% keeping the function name \texttt{asec} (or \texttt{asecd}) as an +% argument for invalid operation exceptions, and a +% \cs{@@_reverse_args:Nww} following precisely that appearing in +% \cs{@@_acos_o:w}. +% \begin{macrocode} +\cs_new:Npn \@@_asec_o:w #1 \s_@@ \@@_chk:w #2#3; @ + { + \if_case:w #2 \exp_stop_f: + \@@_case_use:nw + { \@@_invalid_operation_o:fw { #1 { asec } { asecd } } } + \or: + \@@_case_use:nw + { + \@@_acsc_normal_o:NfwNnw #1 { #1 { asec } { asecd } } + \@@_reverse_args:Nww + } + \or: \@@_case_use:nw { \@@_atan_inf_o:NNNw #1 0 \c_four } + \else: \@@_case_return_same_o:w + \fi: + \s_@@ \@@_chk:w #2 #3; + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[aux, EXP]{\@@_acsc_normal_o:NfwNnw} +% If the exponent is non-positive, the operand is less than~$1$ in +% absolute value, which is always an invalid operation: complain. +% Otherwise, compute the inverse of the operand, and feed it to +% \cs{@@_asin_auxi_o:nNww} (with all the appropriate arguments). This +% computes what we want thanks to +% $\operatorname{acsc}(x)=\operatorname{asin}(1/x)$ and +% $\operatorname{asec}(x)=\operatorname{acos}(1/x)$. +% \begin{macrocode} +\cs_new:Npn \@@_acsc_normal_o:NfwNnw #1#2#3 \s_@@ \@@_chk:w 1#4#5#6; + { + \int_compare:nNnTF {#5} < \c_one + { + \@@_invalid_operation_o:fw {#2} + \s_@@ \@@_chk:w 1#4{#5}#6; + } { - \@@_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 - } + \@@_ep_div:wwwwn + 1,{1000}{0000}{0000}{0000}{0000}{0000}; + #5,#6{0000}{0000}; + { \@@_asin_auxi_o:NnNww #1 {#3} #4 } } } % \end{macrocode} |