% \iffalse meta-comment
%
%% File: l3fp-trig.dtx
%
% Copyright (C) 2011-2026 The LaTeX 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
%
%    https://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 development version of the bundle can be found at
%
%    https://github.com/latex3/latex3
%
% for those people who are interested.
%
%<*driver>
\documentclass[full,kernel]{l3doc}
\begin{document}
  \DocInput{\jobname.dtx}
\end{document}
%</driver>
% \fi
%
% \title{^^A
%   The \pkg{l3fp-trig} module\\
%   Floating point trigonometric functions^^A
% }
% \author{^^A
%  The \LaTeX{} Project\thanks
%    {^^A
%      E-mail:
%        \href{mailto:latex-team@latex-project.org}
%          {latex-team@latex-project.org}^^A
%    }^^A
% }
% \date{Released 2026-06-19}
%
% \maketitle
%
% \begin{documentation}
%
% \end{documentation}
%
% \begin{implementation}
%
% \section{\pkg{l3fp-trig} implementation}
%
%    \begin{macrocode}
%<*code>
%    \end{macrocode}
%
%    \begin{macrocode}
%<@@=fp>
%    \end{macrocode}
%
% \begin{macro}[EXP]
%   {
%     \@@_parse_word_acos:N  ,
%     \@@_parse_word_acosd:N ,
%     \@@_parse_word_acsc:N  ,
%     \@@_parse_word_acscd:N ,
%     \@@_parse_word_asec:N  ,
%     \@@_parse_word_asecd:N ,
%     \@@_parse_word_asin:N  ,
%     \@@_parse_word_asind:N ,
%     \@@_parse_word_cos:N   ,
%     \@@_parse_word_cosd:N  ,
%     \@@_parse_word_cot:N   ,
%     \@@_parse_word_cotd:N  ,
%     \@@_parse_word_csc:N   ,
%     \@@_parse_word_cscd:N  ,
%     \@@_parse_word_sec:N   ,
%     \@@_parse_word_secd:N  ,
%     \@@_parse_word_sin:N   ,
%     \@@_parse_word_sind:N  ,
%     \@@_parse_word_tan:N   ,
%     \@@_parse_word_tand:N  ,
%   }
%   Unary functions.
%    \begin{macrocode}
\tl_map_inline:nn
  {
    {acos} {acsc} {asec} {asin}
    {cos} {cot} {csc} {sec} {sin} {tan}
  }
  {
    \cs_new:cpe { @@_parse_word_#1:N }
      {
        \exp_not:N \@@_parse_unary_function:NNN
        \exp_not:c { @@_#1_o:w }
        \exp_not:N \use_i:nn
      }
    \cs_new:cpe { @@_parse_word_#1d:N }
      {
        \exp_not:N \@@_parse_unary_function:NNN
        \exp_not:c { @@_#1_o:w }
        \exp_not:N \use_ii:nn
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[EXP]
%   {
%     \@@_parse_word_acot:N , \@@_parse_word_acotd:N,
%     \@@_parse_word_atan:N , \@@_parse_word_atand:N,
%   }
%   Those functions may receive a variable number of arguments.
%    \begin{macrocode}
\cs_new:Npn \@@_parse_word_acot:N
  { \@@_parse_function:NNN \@@_acot_o:Nw \use_i:nn }
\cs_new:Npn \@@_parse_word_acotd:N
  { \@@_parse_function:NNN \@@_acot_o:Nw \use_ii:nn }
\cs_new:Npn \@@_parse_word_atan:N
  { \@@_parse_function:NNN \@@_atan_o:Nw \use_i:nn }
\cs_new:Npn \@@_parse_word_atand:N
  { \@@_parse_function:NNN \@@_atan_o:Nw \use_ii:nn }
%    \end{macrocode}
% \end{macro}
%
% \subsection{Direct trigonometric functions}
%
% The approach for all trigonometric functions (sine, cosine, tangent,
% 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
%     $\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$ (in degrees, the same
%     formula with $\pi/4\to 45$), the sign, and the function to
%     compute.
% \end{itemize}
%
% \subsubsection{Filtering special cases}
%
% \begin{macro}[EXP]{\@@_sin_o:w}
%   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} is called to compute the
%   Taylor series: this function receives a sign~|#3|, an initial octant
%   of~$0$, and the function \cs{@@_ep_to_float_o: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\@@_sep: @
  {
    \if_case:w #2 \exp_stop_f:
           \@@_case_return_same_o:w
    \or:   \@@_case_use:nw
             {
               \@@_trig:NNNNNwn #1 \@@_sin_series_o:NNwwww
                 \@@_ep_to_float_o:wwN #3 0
             }
    \or:   \@@_case_use:nw
             { \@@_invalid_operation_o:fw { #1 { sin } { sind } } }
    \else: \@@_case_return_same_o:w
    \fi:
    \s_@@ \@@_chk:w #2 #3 #4\@@_sep:
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[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, the \texttt{trig} function reduces the argument to at
%   most half a right-angle and converts if necessary to radians.  We
%   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 #1 \s_@@ \@@_chk:w #2#3\@@_sep: @
  {
    \if_case:w #2 \exp_stop_f:
           \@@_case_return_o:Nw \c_one_fp
    \or:   \@@_case_use:nw
             {
               \@@_trig:NNNNNwn #1 \@@_sin_series_o:NNwwww
                 \@@_ep_to_float_o:wwN 0 2
             }
    \or:   \@@_case_use:nw
             { \@@_invalid_operation_o:fw { #1 { cos } { cosd } } }
    \else: \@@_case_return_same_o:w
    \fi:
    \s_@@ \@@_chk:w #2 #3\@@_sep:
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[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: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\@@_sep: @
  {
    \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_o:wwN #3 0
             }
    \or:   \@@_case_use:nw
             { \@@_invalid_operation_o:fw { #1 { csc } { cscd } } }
    \else: \@@_case_return_same_o:w
    \fi:
    \s_@@ \@@_chk:w #2 #3 #4\@@_sep:
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[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, 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 #1 \s_@@ \@@_chk:w #2#3\@@_sep: @
  {
    \if_case:w #2 \exp_stop_f:
           \@@_case_return_o:Nw \c_one_fp
    \or:   \@@_case_use:nw
             {
               \@@_trig:NNNNNwn #1 \@@_sin_series_o:NNwwww
                 \@@_ep_inv_to_float_o:wwN 0 2
             }
    \or:   \@@_case_use:nw
             { \@@_invalid_operation_o:fw { #1 { sec } { secd } } }
    \else: \@@_case_return_same_o:w
    \fi:
    \s_@@ \@@_chk:w #2 #3\@@_sep:
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[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.
%   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 #1 \s_@@ \@@_chk:w #2#3#4\@@_sep: @
  {
    \if_case:w #2 \exp_stop_f:
           \@@_case_return_same_o:w
    \or:   \@@_case_use:nw
             {
               \@@_trig:NNNNNwn #1
                 \@@_tan_series_o:NNwwww 0 #3 1
             }
    \or:   \@@_case_use:nw
             { \@@_invalid_operation_o:fw { #1 { tan } { tand } } }
    \else: \@@_case_return_same_o:w
    \fi:
    \s_@@ \@@_chk:w #2 #3 #4\@@_sep:
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[EXP]{\@@_cot_o:w}
% \begin{macro}[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: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: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 #1 \s_@@ \@@_chk:w #2#3#4\@@_sep: @
  {
    \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 3
             }
    \or:   \@@_case_use:nw
             { \@@_invalid_operation_o:fw { #1 { cot } { cotd } } }
    \else: \@@_case_return_same_o:w
    \fi:
    \s_@@ \@@_chk:w #2 #3 #4\@@_sep:
  }
\cs_new:Npn \@@_cot_zero_o:Nfw #1#2#3 \fi:
  {
    \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{Distinguishing small and large arguments}
%
% \begin{macro}[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_o:wN} or \cs{@@_ep_inv_to_float_o: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:NNNNNwn #1#2#3#4#5 \s_@@ \@@_chk:w 1#6#7#8\@@_sep:
  {
    \exp_after:wN #2
    \exp_after:wN #3
    \exp_after:wN #4
    \int_value:w \@@_int_eval:w #5
      \exp_after:wN \exp_after:wN \exp_after:wN \exp_after:wN
      \if_int_compare:w #7 > #1 0 1 \exp_stop_f:
        #1 \@@_trig_large:ww \@@_trigd_large:ww
      \else:
        #1 \@@_trig_small:ww \@@_trigd_small:ww
      \fi:
    #7,#8{0000}{0000}\@@_sep:
  }
%    \end{macrocode}
% \end{macro}
%
% \subsubsection{Small arguments}
%
% \begin{macro}[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 is 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\@@_sep:
  { \@@_ep_to_fixed:wwn #1,#2\@@_sep: . #1,#2\@@_sep: }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[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 \@@_trigd_small:ww #1,#2\@@_sep:
  {
    \@@_ep_mul_raw:wwwwN
      -1,{1745}{3292}{5199}{4329}{5769}{2369}\@@_sep: #1,#2\@@_sep:
    \@@_trig_small:ww
  }
%    \end{macrocode}
% \end{macro}
%
% \subsubsection{Argument reduction in degrees}
%
% \begin{macro}[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\@@_sep:
  {
    \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 \@@_trigd_large_auxi:nnnnwNNNN
    \exp_after:wN \@@_sep:
    \exp:w \exp_end_continue_f:w
    \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\@@_sep: #6#7#8#9
  {
    \exp_after:wN \@@_trigd_large_auxii:wNw
    \int_value:w \@@_int_eval:w #1 + #2
      - (#1 + #2 - 4) / 9 * 9 \@@_int_eval_end:
    #3\@@_sep:
    #4\@@_sep: #5{#6#7#8#9}\@@_sep:
  }
\cs_new:Npn \@@_trigd_large_auxii:wNw #1\@@_sep: #2#3\@@_sep:
  {
    + (#1#2 - 4) / 9 * 2
    \exp_after:wN \@@_trigd_large_auxiii:www
    \int_value:w \@@_int_eval:w #1#2
      - (#1#2 - 4) / 9 * 9 \@@_int_eval_end: #3 \@@_sep:
  }
\cs_new:Npn \@@_trigd_large_auxiii:www #1\@@_sep: #2\@@_sep: #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:
      + 1
    \fi:
    \@@_fixed_sub:wwn {9000}{0000}{0000}{0000}{0000}{0000}\@@_sep:
      {#1}#2{0000}{0000}\@@_sep:
    { \@@_trigd_small:ww 2, }
  }
%    \end{macrocode}
% \end{macro}
%
% \subsubsection{Argument reduction in radians}
%
% 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.
%
% \begin{variable}[EXP]{\c_@@_trig_intarray}
%   This integer array stores blocks of $8$~decimals of
%   $10^{-16}/(2\pi)$.  Each entry is $10^8$ plus an $8$~digit number
%   storing $8$ decimals.  In total we store $10112$~decimals of
%   $10^{-16}/(2\pi)$.  The number of decimals we really need is the
%   maximum exponent plus the number of digits we later need,~$52$,
%   plus~$12$ ($4-1$~groups of $4$~digits).  The memory footprint ($1/2$
%   byte per digit) is the same as an earlier method of storing the data
%   as a control sequence name, but the major advantage is that we can
%   unpack specific subsets of the digits without unpacking the $10112$
%   decimals.
%    \begin{macrocode}
\intarray_const_from_clist:Nn \c_@@_trig_intarray
  {
    100000000, 100000000, 115915494, 130918953, 135768883, 176337251,
    143620344, 159645740, 145644874, 176673440, 158896797, 163422653,
    150901138, 102766253, 108595607, 128427267, 157958036, 189291184,
    161145786, 152877967, 141073169, 198392292, 139966937, 140907757,
    130777463, 196925307, 168871739, 128962173, 197661693, 136239024,
    117236290, 111832380, 111422269, 197557159, 140461890, 108690267,
    139561204, 189410936, 193784408, 155287230, 199946443, 140024867,
    123477394, 159610898, 132309678, 130749061, 166986462, 180469944,
    186521878, 181574786, 156696424, 110389958, 174139348, 160998386,
    180991999, 162442875, 158517117, 188584311, 117518767, 116054654,
    175369880, 109739460, 136475933, 137680593, 102494496, 163530532,
    171567755, 103220324, 177781639, 171660229, 146748119, 159816584,
    106060168, 103035998, 113391198, 174988327, 186654435, 127975507,
    100162406, 177564388, 184957131, 108801221, 199376147, 168137776,
    147378906, 133068046, 145797848, 117613124, 127314069, 196077502,
    145002977, 159857089, 105690279, 167851315, 125210016, 131774602,
    109248116, 106240561, 145620314, 164840892, 148459191, 143521157,
    154075562, 100871526, 160680221, 171591407, 157474582, 172259774,
    162853998, 175155329, 139081398, 117724093, 158254797, 107332871,
    190406999, 175907657, 170784934, 170393589, 182808717, 134256403,
    166895116, 162545705, 194332763, 112686500, 126122717, 197115321,
    112599504, 138667945, 103762556, 108363171, 116952597, 158128224,
    194162333, 143145106, 112353687, 185631136, 136692167, 114206974,
    169601292, 150578336, 105311960, 185945098, 139556718, 170995474,
    165104316, 123815517, 158083944, 129799709, 199505254, 138756612,
    194458833, 106846050, 178529151, 151410404, 189298850, 163881607,
    176196993, 107341038, 199957869, 118905980, 193737772, 106187543,
    122271893, 101366255, 126123878, 103875388, 181106814, 106765434,
    108282785, 126933426, 179955607, 107903860, 160352738, 199624512,
    159957492, 176297023, 159409558, 143011648, 129641185, 157771240,
    157544494, 157021789, 176979240, 194903272, 194770216, 164960356,
    153181535, 144003840, 168987471, 176915887, 163190966, 150696440,
    147769706, 187683656, 177810477, 197954503, 153395758, 130188183,
    186879377, 166124814, 195305996, 155802190, 183598751, 103512712,
    190432315, 180498719, 168687775, 194656634, 162210342, 104440855,
    149785037, 192738694, 129353661, 193778292, 187359378, 143470323,
    102371458, 137923557, 111863634, 119294601, 183182291, 196416500,
    187830793, 131353497, 179099745, 186492902, 167450609, 189368909,
    145883050, 133703053, 180547312, 132158094, 131976760, 132283131,
    141898097, 149822438, 133517435, 169898475, 101039500, 168388003,
    197867235, 199608024, 100273901, 108749548, 154787923, 156826113,
    199489032, 168997427, 108349611, 149208289, 103776784, 174303550,
    145684560, 183671479, 130845672, 133270354, 185392556, 120208683,
    193240995, 162211753, 131839402, 109707935, 170774965, 149880868,
    160663609, 168661967, 103747454, 121028312, 119251846, 122483499,
    111611495, 166556037, 196967613, 199312829, 196077608, 127799010,
    107830360, 102338272, 198790854, 102387615, 157445430, 192601191,
    100543379, 198389046, 154921248, 129516070, 172853005, 122721023,
    160175233, 113173179, 175931105, 103281551, 109373913, 163964530,
    157926071, 180083617, 195487672, 146459804, 173977292, 144810920,
    109371257, 186918332, 189588628, 139904358, 168666639, 175673445,
    114095036, 137327191, 174311388, 106638307, 125923027, 159734506,
    105482127, 178037065, 133778303, 121709877, 134966568, 149080032,
    169885067, 141791464, 168350828, 116168533, 114336160, 173099514,
    198531198, 119733758, 144420984, 116559541, 152250643, 139431286,
    144403838, 183561508, 179771645, 101706470, 167518774, 156059160,
    187168578, 157939226, 123475633, 117111329, 198655941, 159689071,
    198506887, 144230057, 151919770, 156900382, 118392562, 120338742,
    135362568, 108354156, 151729710, 188117217, 195936832, 156488518,
    174997487, 108553116, 159830610, 113921445, 144601614, 188452770,
    125114110, 170248521, 173974510, 138667364, 103872860, 109967489,
    131735618, 112071174, 104788993, 168886556, 192307848, 150230570,
    157144063, 163863202, 136852010, 174100574, 185922811, 115721968,
    100397824, 175953001, 166958522, 112303464, 118773650, 143546764,
    164565659, 171901123, 108476709, 193097085, 191283646, 166919177,
    169387914, 133315566, 150669813, 121641521, 100895711, 172862384,
    126070678, 145176011, 113450800, 169947684, 122356989, 162488051,
    157759809, 153397080, 185475059, 175362656, 149034394, 145420581,
    178864356, 183042000, 131509559, 147434392, 152544850, 167491429,
    108647514, 142303321, 133245695, 111634945, 167753939, 142403609,
    105438335, 152829243, 142203494, 184366151, 146632286, 102477666,
    166049531, 140657343, 157553014, 109082798, 180914786, 169343492,
    127376026, 134997829, 195701816, 119643212, 133140475, 176289748,
    140828911, 174097478, 126378991, 181699939, 148749771, 151989818,
    172666294, 160183053, 195832752, 109236350, 168538892, 128468247,
    125997252, 183007668, 156937583, 165972291, 198244297, 147406163,
    181831139, 158306744, 134851692, 185973832, 137392662, 140243450,
    119978099, 140402189, 161348342, 173613676, 144991382, 171541660,
    163424829, 136374185, 106122610, 186132119, 198633462, 184709941,
    183994274, 129559156, 128333990, 148038211, 175011612, 111667205,
    119125793, 103552929, 124113440, 131161341, 112495318, 138592695,
    184904438, 146807849, 109739828, 108855297, 104515305, 139914009,
    188698840, 188365483, 166522246, 168624087, 125401404, 100911787,
    142122045, 123075334, 173972538, 114940388, 141905868, 142311594,
    163227443, 139066125, 116239310, 162831953, 123883392, 113153455,
    163815117, 152035108, 174595582, 101123754, 135976815, 153401874,
    107394340, 136339780, 138817210, 104531691, 182951948, 179591767,
    139541778, 179243527, 161740724, 160593916, 102732282, 187946819,
    136491289, 149714953, 143255272, 135916592, 198072479, 198580612,
    169007332, 118844526, 179433504, 155801952, 149256630, 162048766,
    116134365, 133992028, 175452085, 155344144, 109905129, 182727454,
    165911813, 122232840, 151166615, 165070983, 175574337, 129548631,
    120411217, 116380915, 160616116, 157320000, 183306114, 160618128,
    103262586, 195951602, 146321661, 138576614, 180471993, 127077713,
    116441201, 159496011, 106328305, 120759583, 148503050, 179095584,
    198298218, 167402898, 138551383, 123957020, 180763975, 150429225,
    198476470, 171016426, 197438450, 143091658, 164528360, 132493360,
    143546572, 137557916, 113663241, 120457809, 196971566, 134022158,
    180545794, 131328278, 100552461, 132088901, 187421210, 192448910,
    141005215, 149680971, 113720754, 100571096, 134066431, 135745439,
    191597694, 135788920, 179342561, 177830222, 137011486, 142492523,
    192487287, 113132021, 176673607, 156645598, 127260957, 141566023,
    143787436, 129132109, 174858971, 150713073, 191040726, 143541417,
    197057222, 165479803, 181512759, 157912400, 125344680, 148220261,
    173422990, 101020483, 106246303, 137964746, 178190501, 181183037,
    151538028, 179523433, 141955021, 135689770, 191290561, 143178787,
    192086205, 174499925, 178975690, 118492103, 124206471, 138519113,
    188147564, 102097605, 154895793, 178514140, 141453051, 151583964,
    128232654, 106020603, 131189158, 165702720, 186250269, 191639375,
    115278873, 160608114, 155694842, 110322407, 177272742, 116513642,
    134366992, 171634030, 194053074, 180652685, 109301658, 192136921,
    141431293, 171341061, 157153714, 106203978, 147618426, 150297807,
    186062669, 169960809, 118422347, 163350477, 146719017, 145045144,
    161663828, 146208240, 186735951, 102371302, 190444377, 194085350,
    134454426, 133413062, 163074595, 113830310, 122931469, 134466832,
    185176632, 182415152, 110179422, 164439571, 181217170, 121756492,
    119644493, 196532222, 118765848, 182445119, 109401340, 150443213,
    198586286, 121083179, 139396084, 143898019, 114787389, 177233102,
    186310131, 148695521, 126205182, 178063494, 157118662, 177825659,
    188310053, 151552316, 165984394, 109022180, 163144545, 121212978,
    197344714, 188741258, 126822386, 102360271, 109981191, 152056882,
    134723983, 158013366, 106837863, 128867928, 161973236, 172536066,
    185216856, 132011948, 197807339, 158419190, 166595838, 167852941,
    124187182, 117279875, 106103946, 106481958, 157456200, 160892122,
    184163943, 173846549, 158993202, 184812364, 133466119, 170732430,
    195458590, 173361878, 162906318, 150165106, 126757685, 112163575,
    188696307, 145199922, 100107766, 176830946, 198149756, 122682434,
    179367131, 108412102, 119520899, 148191244, 140487511, 171059184,
    141399078, 189455775, 118462161, 190415309, 134543802, 180893862,
    180732375, 178615267, 179711433, 123241969, 185780563, 176301808,
    184386640, 160717536, 183213626, 129671224, 126094285, 140110963,
    121826276, 151201170, 122552929, 128965559, 146082049, 138409069,
    107606920, 103954646, 119164002, 115673360, 117909631, 187289199,
    186343410, 186903200, 157966371, 103128612, 135698881, 176403642,
    152540837, 109810814, 183519031, 121318624, 172281810, 150845123,
    169019064, 166322359, 138872454, 163073727, 128087898, 130041018,
    194859136, 173742589, 141812405, 167291912, 138003306, 134499821,
    196315803, 186381054, 124578934, 150084553, 128031351, 118843410,
    107373060, 159565443, 173624887, 171292628, 198074235, 139074061,
    178690578, 144431052, 174262641, 176783005, 182214864, 162289361,
    192966929, 192033046, 169332843, 181580535, 164864073, 118444059,
    195496893, 153773183, 167266131, 130108623, 158802128, 180432893,
    144562140, 147978945, 142337360, 158506327, 104399819, 132635916,
    168734194, 136567839, 101281912, 120281622, 195003330, 112236091,
    185875592, 101959081, 122415367, 194990954, 148881099, 175891989,
    108115811, 163538891, 163394029, 123722049, 184837522, 142362091,
    100834097, 156679171, 100841679, 157022331, 178971071, 102928884,
    189701309, 195339954, 124415335, 106062584, 139214524, 133864640,
    134324406, 157317477, 155340540, 144810061, 177612569, 108474646,
    114329765, 143900008, 138265211, 145210162, 136643111, 197987319,
    102751191, 144121361, 169620456, 193602633, 161023559, 162140467,
    102901215, 167964187, 135746835, 187317233, 110047459, 163339773,
    124770449, 118885134, 141536376, 100915375, 164267438, 145016622,
    113937193, 106748706, 128815954, 164819775, 119220771, 102367432,
    189062690, 170911791, 194127762, 112245117, 123546771, 115640433,
    135772061, 166615646, 174474627, 130562291, 133320309, 153340551,
    138417181, 194605321, 150142632, 180008795, 151813296, 175497284,
    167018836, 157425342, 150169942, 131069156, 134310662, 160434122,
    105213831, 158797111, 150754540, 163290657, 102484886, 148697402,
    187203725, 198692811, 149360627, 140384233, 128749423, 132178578,
    177507355, 171857043, 178737969, 134023369, 102911446, 196144864,
    197697194, 134527467, 144296030, 189437192, 154052665, 188907106,
    162062575, 150993037, 199766583, 167936112, 181374511, 104971506,
    115378374, 135795558, 167972129, 135876446, 130937572, 103221320,
    124605656, 161129971, 131027586, 191128460, 143251843, 143269155,
    129284585, 173495971, 150425653, 199302112, 118494723, 121323805,
    116549802, 190991967, 168151180, 122483192, 151273721, 199792134,
    133106764, 121874844, 126215985, 112167639, 167793529, 182985195,
    185453921, 106957880, 158685312, 132775454, 133229161, 198905318,
    190537253, 191582222, 192325972, 178133427, 181825606, 148823337,
    160719681, 101448145, 131983362, 137910767, 112550175, 128826351,
    183649210, 135725874, 110356573, 189469487, 154446940, 118175923,
    106093708, 128146501, 185742532, 149692127, 164624247, 183221076,
    154737505, 168198834, 156410354, 158027261, 125228550, 131543250,
    139591848, 191898263, 104987591, 115406321, 103542638, 190012837,
    142615518, 178773183, 175862355, 117537850, 169565995, 170028011,
    158412588, 170150030, 117025916, 174630208, 142412449, 112839238,
    105257725, 114737141, 123102301, 172563968, 130555358, 132628403,
    183638157, 168682846, 143304568, 105994018, 170010719, 152092970,
    117799058, 132164175, 179868116, 158654714, 177489647, 116547948,
    183121404, 131836079, 184431405, 157311793, 149677763, 173989893,
    102277656, 107058530, 140837477, 152640947, 143507039, 152145247,
    101683884, 107090870, 161471944, 137225650, 128231458, 172995869,
    173831689, 171268519, 139042297, 111072135, 107569780, 137262545,
    181410950, 138270388, 198736451, 162848201, 180468288, 120582913,
    153390138, 135649144, 130040157, 106509887, 192671541, 174507066,
    186888783, 143805558, 135011967, 145862340, 180595327, 124727843,
    182925939, 157715840, 136885940, 198993925, 152416883, 178793572,
    179679516, 154076673, 192703125, 164187609, 162190243, 104699348,
    159891990, 160012977, 174692145, 132970421, 167781726, 115178506,
    153008552, 155999794, 102099694, 155431545, 127458567, 104403686,
    168042864, 184045128, 181182309, 179349696, 127218364, 192935516,
    120298724, 169583299, 148193297, 183358034, 159023227, 105261254,
    121144370, 184359584, 194433836, 138388317, 175184116, 108817112,
    151279233, 137457721, 193398208, 119005406, 132929377, 175306906,
    160741530, 149976826, 147124407, 176881724, 186734216, 185881509,
    191334220, 175930947, 117385515, 193408089, 157124410, 163472089,
    131949128, 180783576, 131158294, 100549708, 191802336, 165960770,
    170927599, 101052702, 181508688, 197828549, 143403726, 142729262,
    110348701, 139928688, 153550062, 106151434, 130786653, 196085995,
    100587149, 139141652, 106530207, 100852656, 124074703, 166073660,
    153338052, 163766757, 120188394, 197277047, 122215363, 138511354,
    183463624, 161985542, 159938719, 133367482, 104220974, 149956672,
    170250544, 164232439, 157506869, 159133019, 137469191, 142980999,
    134242305, 150172665, 121209241, 145596259, 160554427, 159095199,
    168243130, 184279693, 171132070, 121049823, 123819574, 171759855,
    119501864, 163094029, 175943631, 194450091, 191506160, 149228764,
    132319212, 197034460, 193584259, 126727638, 168143633, 109856853,
    127860243, 132141052, 133076065, 188414958, 158718197, 107124299,
    159592267, 181172796, 144388537, 196763139, 127431422, 179531145,
    100064922, 112650013, 132686230, 121550837,
  }
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}[rEXP]
%   {
%     \@@_trig_large:ww,
%     \@@_trig_large_auxi:w,
%     \@@_trig_large_auxii:w,
%     \@@_trig_large_auxiii:w,
%   }
%   The exponent~|#1| is between $1$ and~$\ExplSyntaxOn \int_use:N
%   \c__fp_max_exponent_int$.  We wish to look up decimals
%   $10^{\text{\texttt{\#1}}-16}/(2\pi)$ starting from the digit
%   $|#1|+1$.  Since they are stored in batches of~$8$, compute
%   $\lfloor|#1|/8\rfloor$ and fetch blocks of $8$ digits starting
%   there.  The numbering of items in \cs{c_@@_trig_intarray} starts
%   at~$1$, so the block $\lfloor|#1|/8\rfloor+1$ contains the digit we
%   want, at one of the eight positions.  Each call to \cs{int_value:w}
%   \cs{__kernel_intarray_item:Nn} expands the next, until being stopped
%   by \cs{@@_trig_large_auxiii:w} using \cs{exp_stop_f:}.  Once all
%   these blocks are unpacked, the \cs{exp_stop_f:} and $0$ to $7$
%   digits are removed by \cs[no-index]{use_none:n\ldots{}n}.
%   Finally, \cs{@@_trig_large_auxii:w} packs $64$ digits (there are
%   between $65$ and $72$ at this point) 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\@@_sep:
  {
    \exp_after:wN \@@_trig_large_auxi:w
    \int_value:w \@@_int_eval:w (#1 - 4) / 8 \exp_after:wN ,
    \int_value:w #1 , \@@_sep:
    {#2}{#3}{#4}{#5} \@@_sep:
  }
\cs_new:Npn \@@_trig_large_auxi:w #1, #2,
  {
    \exp_after:wN \exp_after:wN
    \exp_after:wN \@@_trig_large_auxii:w
    \cs:w
      use_none:n \prg_replicate:nn { #2 - #1 * 8 } { n }
      \exp_after:wN
    \cs_end:
    \int_value:w
    \__kernel_intarray_item:Nn \c_@@_trig_intarray
      { \@@_int_eval:w #1 + 1 \scan_stop: }
    \exp_after:wN \@@_trig_large_auxiii:w \int_value:w
    \__kernel_intarray_item:Nn \c_@@_trig_intarray
      { \@@_int_eval:w #1 + 2 \scan_stop: }
    \exp_after:wN \@@_trig_large_auxiii:w \int_value:w
    \__kernel_intarray_item:Nn \c_@@_trig_intarray
      { \@@_int_eval:w #1 + 3 \scan_stop: }
    \exp_after:wN \@@_trig_large_auxiii:w \int_value:w
    \__kernel_intarray_item:Nn \c_@@_trig_intarray
      { \@@_int_eval:w #1 + 4 \scan_stop: }
    \exp_after:wN \@@_trig_large_auxiii:w \int_value:w
    \__kernel_intarray_item:Nn \c_@@_trig_intarray
      { \@@_int_eval:w #1 + 5 \scan_stop: }
    \exp_after:wN \@@_trig_large_auxiii:w \int_value:w
    \__kernel_intarray_item:Nn \c_@@_trig_intarray
      { \@@_int_eval:w #1 + 6 \scan_stop: }
    \exp_after:wN \@@_trig_large_auxiii:w \int_value:w
    \__kernel_intarray_item:Nn \c_@@_trig_intarray
      { \@@_int_eval:w #1 + 7 \scan_stop: }
    \exp_after:wN \@@_trig_large_auxiii:w \int_value:w
    \__kernel_intarray_item:Nn \c_@@_trig_intarray
      { \@@_int_eval:w #1 + 8 \scan_stop: }
    \exp_after:wN \@@_trig_large_auxiii:w \int_value:w
    \__kernel_intarray_item:Nn \c_@@_trig_intarray
      { \@@_int_eval:w #1 + 9 \scan_stop: }
    \exp_stop_f:
  }
\cs_new:Npn \@@_trig_large_auxii:w
  {
    \@@_pack_twice_four:wNNNNNNNN \@@_pack_twice_four:wNNNNNNNN
    \@@_pack_twice_four:wNNNNNNNN \@@_pack_twice_four:wNNNNNNNN
    \@@_pack_twice_four:wNNNNNNNN \@@_pack_twice_four:wNNNNNNNN
    \@@_pack_twice_four:wNNNNNNNN \@@_pack_twice_four:wNNNNNNNN
    \@@_trig_large_auxv:www \@@_sep:
  }
\cs_new:Npn \@@_trig_large_auxiii:w 1 { \exp_stop_f: }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[rEXP]
%   {
%     \@@_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 \cs{@@_sep:}.  Then a few more digits of the
%   same fractional part, ending with a \cs{@@_sep:}, 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 \cs{@@_sep:} closes the ladder, and
%   we return control to the \texttt{auxvii} auxiliary.
%    \begin{macrocode}
\cs_new:Npn \@@_trig_large_auxv:www #1\@@_sep: #2\@@_sep: #3\@@_sep:
  {
    \exp_after:wN \@@_use_i_until_s:nw
    \exp_after:wN \@@_trig_large_auxvii:w
    \int_value:w \@@_int_eval:w \c_@@_leading_shift_int
      \prg_replicate:nn { 13 }
        { \@@_trig_large_auxvi:wnnnnnnnn }
      + \c_@@_trailing_shift_int - \c_@@_middle_shift_int
      \@@_use_i_until_s:nw
      \@@_sep: #3 #1 \@@_sep: \@@_sep:
  }
\cs_new:Npn \@@_trig_large_auxvi:wnnnnnnnn #1\@@_sep: #2#3#4#5#6#7#8#9
  {
    \exp_after:wN \@@_trig_large_pack:NNNNNw
    \int_value:w \@@_int_eval:w \c_@@_middle_shift_int
      + #2*#9 + #3*#8 + #4*#7 + #5*#6
      #1\@@_sep: {#2}{#3}{#4}{#5} {#7}{#8}{#9}
  }
\cs_new:Npn \@@_trig_large_pack:NNNNNw #1#2#3#4#5#6\@@_sep:
  { + #1#2#3#4#5 \@@_sep: #6 }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[rEXP]
%   {
%     \@@_trig_large_auxvii:w,
%     \@@_trig_large_auxviii:w,
%   }
% \begin{macro}[EXP]
%   {
%     \@@_trig_large_auxix:Nw,
%     \@@_trig_large_auxx:wNNNNN,
%     \@@_trig_large_auxxi:w
%   }
%   The \texttt{auxvii} auxiliary is followed by $52$~digits and a
%   \cs{@@_sep:}.  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_value:w \@@_int_eval:w (#1#2#3 - 62) / 125 \@@_sep:
    #1#2#3
  }
\cs_new:Npn \@@_trig_large_auxviii:ww #1\@@_sep:
  {
    + #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:
  }
\cs_new:Npn \@@_trig_large_auxix:Nw
  {
    \exp_after:wN \@@_use_i_until_s:nw
    \exp_after:wN \@@_trig_large_auxxi:w
    \int_value:w \@@_int_eval:w \c_@@_leading_shift_int
      \prg_replicate:nn { 13 }
        { \@@_trig_large_auxx:wNNNNN }
      + \c_@@_trailing_shift_int - \c_@@_middle_shift_int
      \@@_sep:
  }
\cs_new:Npn \@@_trig_large_auxx:wNNNNN #1\@@_sep: #2 #3#4#5#6
  {
    \exp_after:wN \@@_trig_large_pack:NNNNNw
    \int_value:w \@@_int_eval:w \c_@@_middle_shift_int
      #2 8 * #3#4#5#6
      #1\@@_sep: #2
  }
\cs_new:Npn \@@_trig_large_auxxi:w #1\@@_sep:
  {
    \exp_after:wN \@@_ep_mul_raw:wwwwN
    \int_value:w \@@_int_eval:w 0 \@@_ep_to_ep_loop:N #1 \@@_sep: \@@_sep: !
    0,{7853}{9816}{3397}{4483}{0961}{5661}\@@_sep:
    \@@_trig_small:ww
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \subsubsection{Computing the power series}
%
% \begin{macro}[EXP]
%   {\@@_sin_series_o:NNwwww, \@@_sin_series_aux_o:NNnwww}
%   Here we receive a conversion function \cs{@@_ep_to_float_o:wwN} or
%   \cs{@@_ep_inv_to_float_o:wwN}, a \meta{sign} ($0$ or~$2$), a
%   (non-negative) \meta{octant} delimited by a dot, a \meta{fixed
%     point} number delimited by a \cs{@@_sep:}, and an extended-precision
%   number.  The auxiliary receives:
%   \begin{itemize}
%   \item the conversion function~|#1|;
%   \item the final sign, which depends on the octant~|#3| and the
%     sign~|#2|;
%   \item the octant~|#3|, which controls 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
%   \[
%   \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 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_o:NNwwww #1#2#3. #4\@@_sep:
  {
    \@@_fixed_mul:wwn #4\@@_sep: #4\@@_sep:
    {
      \exp_after:wN \@@_sin_series_aux_o:NNnwww
      \exp_after:wN #1
      \int_value:w
        \if_int_odd:w \@@_int_eval:w (#3 + 2) / 4 \@@_int_eval_end:
          #2
        \else:
          \if_meaning:w #2 0 2 \else: 0 \fi:
        \fi:
      {#3}
    }
  }
\cs_new:Npn \@@_sin_series_aux_o:NNnwww #1#2#3 #4\@@_sep: #5,#6\@@_sep:
  {
    \if_int_odd:w \@@_int_eval:w #3 / 2 \@@_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}\@@_sep:
                                  #4\@@_sep:
                                  {0000}{0000}{0000}{0477}{9477}{3324}\@@_sep:
      \@@_fixed_mul_sub_back:wwwn #4\@@_sep:
                                  {0000}{0000}{0011}{4707}{4559}{7730}\@@_sep:
      \@@_fixed_mul_sub_back:wwwn #4\@@_sep:
                                  {0000}{0000}{2087}{6756}{9878}{6810}\@@_sep:
      \@@_fixed_mul_sub_back:wwwn #4\@@_sep:
                                  {0000}{0027}{5573}{1922}{3985}{8907}\@@_sep:
      \@@_fixed_mul_sub_back:wwwn #4\@@_sep:
                                  {0000}{2480}{1587}{3015}{8730}{1587}\@@_sep:
      \@@_fixed_mul_sub_back:wwwn #4\@@_sep:
                                  {0013}{8888}{8888}{8888}{8888}{8889}\@@_sep:
      \@@_fixed_mul_sub_back:wwwn #4\@@_sep:
                                  {0416}{6666}{6666}{6666}{6666}{6667}\@@_sep:
      \@@_fixed_mul_sub_back:wwwn #4\@@_sep:
                                  {5000}{0000}{0000}{0000}{0000}{0000}\@@_sep:
      \@@_fixed_mul_sub_back:wwwn#4\@@_sep:
                                  {10000}{0000}{0000}{0000}{0000}{0000}\@@_sep:
      { \@@_fixed_continue:wn 0, }
    }
    { % 1/17!
      \@@_fixed_mul_sub_back:wwwn {0000}{0000}{0000}{0028}{1145}{7254}\@@_sep:
                                  #4\@@_sep:
                                  {0000}{0000}{0000}{7647}{1637}{3182}\@@_sep:
      \@@_fixed_mul_sub_back:wwwn #4\@@_sep:
                                  {0000}{0000}{0160}{5904}{3836}{8216}\@@_sep:
      \@@_fixed_mul_sub_back:wwwn #4\@@_sep:
                                  {0000}{0002}{5052}{1083}{8544}{1719}\@@_sep:
      \@@_fixed_mul_sub_back:wwwn #4\@@_sep:
                                  {0000}{0275}{5731}{9223}{9858}{9065}\@@_sep:
      \@@_fixed_mul_sub_back:wwwn #4\@@_sep:
                                  {0001}{9841}{2698}{4126}{9841}{2698}\@@_sep:
      \@@_fixed_mul_sub_back:wwwn #4\@@_sep:
                                  {0083}{3333}{3333}{3333}{3333}{3333}\@@_sep:
      \@@_fixed_mul_sub_back:wwwn #4\@@_sep:
                                  {1666}{6666}{6666}{6666}{6666}{6667}\@@_sep:
      \@@_fixed_mul_sub_back:wwwn#4\@@_sep:
                                  {10000}{0000}{0000}{0000}{0000}{0000}\@@_sep:
      { \@@_ep_mul:wwwwn 0, } #5,#6\@@_sep:
    }
    {
      \exp_after:wN \@@_sanitize:Nw
      \exp_after:wN #2
      \int_value:w \@@_int_eval:w #1
    }
    #2
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[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
%   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 (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 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:NNwwww #1#2#3. #4\@@_sep:
  {
    \@@_fixed_mul:wwn #4\@@_sep: #4\@@_sep:
    {
      \exp_after:wN \@@_tan_series_aux_o:Nnwww
      \int_value:w
        \if_int_odd:w \@@_int_eval:w #3 / 2 \@@_int_eval_end:
          \exp_after:wN \reverse_if:N
        \fi:
        \if_meaning:w #1#2 2 \else: 0 \fi:
      {#3}
    }
  }
\cs_new:Npn \@@_tan_series_aux_o:Nnwww #1 #2 #3\@@_sep: #4,#5\@@_sep:
  {
    \@@_fixed_mul_sub_back:wwwn {0000}{0000}{1527}{3493}{0856}{7059}\@@_sep:
                                #3\@@_sep:
                                {0000}{0159}{6080}{0274}{5257}{6472}\@@_sep:
    \@@_fixed_mul_sub_back:wwwn #3\@@_sep:
                                {0002}{4571}{2320}{0157}{2558}{8481}\@@_sep:
    \@@_fixed_mul_sub_back:wwwn #3\@@_sep:
                                {0115}{5830}{7533}{5397}{3168}{2147}\@@_sep:
    \@@_fixed_mul_sub_back:wwwn #3\@@_sep:
                                {1929}{8245}{6140}{3508}{7719}{2982}\@@_sep:
    \@@_fixed_mul_sub_back:wwwn #3\@@_sep:
                                {10000}{0000}{0000}{0000}{0000}{0000}\@@_sep:
    { \@@_ep_mul:wwwwn 0, } #4,#5\@@_sep:
    {
      \@@_fixed_mul_sub_back:wwwn {0000}{0007}{0258}{0681}{9408}{4706}\@@_sep:
                                  #3\@@_sep:
                                  {0000}{2343}{7175}{1399}{6151}{7670}\@@_sep:
      \@@_fixed_mul_sub_back:wwwn #3\@@_sep:
                                  {0019}{2638}{4588}{9232}{8861}{3691}\@@_sep:
      \@@_fixed_mul_sub_back:wwwn #3\@@_sep:
                                  {0536}{6357}{0691}{4344}{6852}{4252}\@@_sep:
      \@@_fixed_mul_sub_back:wwwn #3\@@_sep:
                                  {5263}{1578}{9473}{6842}{1052}{6315}\@@_sep:
      \@@_fixed_mul_sub_back:wwwn#3\@@_sep:
                                  {10000}{0000}{0000}{0000}{0000}{0000}\@@_sep:
      {
        \reverse_if:N \if_int_odd:w
            \@@_int_eval:w (#2 - 1) / 2 \@@_int_eval_end:
          \exp_after:wN \@@_reverse_args:Nww
        \fi:
        \@@_ep_div:wwwwn 0,
      }
    }
    {
      \exp_after:wN \@@_sanitize:Nw
      \exp_after:wN #1
      \int_value:w \@@_int_eval:w \@@_ep_to_float_o:wwN
    }
    #1
  }
%    \end{macrocode}
% \end{macro}
%
% \subsection{Inverse trigonometric functions}
%
% 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 \(\operatorname{atan}(y, x) =
% \operatorname{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 \(\operatorname{atan}\) as
% \begin{align}
%   \operatorname{acos} x & = \operatorname{atan}(\sqrt{1-x^2}, x) \\
%   \operatorname{asin} x & = \operatorname{atan}(x, \sqrt{1-x^2}) \\
%   \operatorname{asec} x & = \operatorname{atan}(\sqrt{x^2-1}, 1) \\
%   \operatorname{acsc} x & = \operatorname{atan}(1, \sqrt{x^2-1}) \\
%   \operatorname{atan} x & = \operatorname{atan}(x, 1) \\
%   \operatorname{acot} x & = \operatorname{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 $\operatorname{atan}(y, x) = \operatorname{atan}(y
% / x)$ is the angular coordinate of the point $(x, y)$.
%
% As for direct trigonometric functions, the first step in computing
% $\operatorname{atan}(y, x)$ is argument reduction.  The sign of~$y$ gives 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 \enquote{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
%   $\operatorname{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
%   $\operatorname{atan}\frac{\lvert y\rvert}{x}
%   = \frac{\pi}{4}-\operatorname{atan}\frac{x-\lvert y\rvert}{x+\lvert y\rvert}$;
% \item[2] $0 < 0.41421 \lvert y\rvert < x < \lvert y\rvert$, then
%   $\operatorname{atan}\frac{\lvert y\rvert}{x}
%   = \frac{\pi}{4}+\operatorname{atan}\frac{-x+\lvert y\rvert}{x+\lvert y\rvert}$;
% \item[3] $0 < x < 0.41421 \lvert y\rvert$, then
%   $\operatorname{atan}\frac{\lvert y\rvert}{x}
%   = \frac{\pi}{2}-\operatorname{atan}\frac{x}{\lvert y\rvert}$;
% \item[4] $0 < -x < 0.41421 \lvert y\rvert$, then
%   $\operatorname{atan}\frac{\lvert y\rvert}{x}
%   = \frac{\pi}{2}+\operatorname{atan}\frac{-x}{\lvert y\rvert}$;
% \item[5] $0 < 0.41421 \lvert y\rvert < -x < \lvert y\rvert$, then
%   $\operatorname{atan}\frac{\lvert y\rvert}{x}
%   =\frac{3\pi}{4}-\operatorname{atan}\frac{x+\lvert y\rvert}{-x+\lvert y\rvert}$;
% \item[6] $0 < -0.41421 x < \lvert y\rvert < -x$, then
%   $\operatorname{atan}\frac{\lvert y\rvert}{x}
%   =\frac{3\pi}{4}+\operatorname{atan}\frac{-x-\lvert y\rvert}{-x+\lvert y\rvert}$;
% \item[7] $0 < \lvert y\rvert < -0.41421 x$, then
%   $\operatorname{atan}\frac{\lvert y\rvert}{x}
%   = \pi-\operatorname{atan}\frac{\lvert y\rvert}{-x}$.
% \end{itemize}
% In the following, we 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}[EXP]{\@@_atan_o:Nw, \@@_acot_o:Nw, \@@_atan_default:w}
%   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.  The helper
%   \cs{@@_parse_function_one_two:nnw} checks that the operand is one or
%   two floating point numbers (not tuples) and leaves its second
%   argument or its tail accordingly (its first argument is used for
%   error messages).  More precisely if we are given a single floating
%   point number \cs{@@_atan_default:w} places \cs{c_one_fp} (expanded)
%   after it; otherwise \cs{@@_atan_default:w} is omitted by
%   \cs{@@_parse_function_one_two:nnw}.
%    \begin{macrocode}
\cs_new:Npn \@@_atan_o:Nw #1
  {
    \@@_parse_function_one_two:nnw
      { #1 { atan } { atand } }
      { \@@_atan_default:w \@@_atanii_o:Nww #1 }
  }
\cs_new:Npn \@@_acot_o:Nw #1
  {
    \@@_parse_function_one_two:nnw
      { #1 { acot } { acotd } }
      { \@@_atan_default:w \@@_acotii_o:Nww #1 }
  }
\cs_new:Npe \@@_atan_default:w #1#2#3 @ { #1 #2 #3 \c_one_fp @ }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[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) = \operatorname{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\@@_sep: \s_@@ \@@_chk:w #5 #6 @
  {
    \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 10 \else: 0 \fi:
      \else:
        \if_int_compare:w #2 > #5 \exp_stop_f: 1 \else: 2 \fi:
      \fi:
      \exp_stop_f:
         \@@_case_return:nw { \@@_atan_inf_o:NNNw #1 #3 2 }
    \or: \@@_case_return:nw { \@@_atan_inf_o:NNNw #1 #3 4 }
    \or: \@@_case_return:nw { \@@_atan_inf_o:NNNw #1 #3 0 }
    \fi:
    \@@_atan_normal_o:NNnwNnw #1
    \s_@@ \@@_chk:w #2#3#4\@@_sep:
    \s_@@ \@@_chk:w #5 #6
  }
\cs_new:Npn \@@_acotii_o:Nww #1#2\@@_sep: #3\@@_sep:
  { \@@_atanii_o:Nww #1#3\@@_sep: #2\@@_sep: }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[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|; $\operatorname{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, $\operatorname{atan} z$ is computed to be~$0$,
%   and the result is $[|#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\@@_sep:
  {
    \exp_after:wN \@@_atan_combine_o:NwwwwwN
    \exp_after:wN #2
    \int_value:w \@@_int_eval:w
      \if_meaning:w 2 #5 7 - \fi: #3 \exp_after:wN \@@_sep:
    \c_@@_one_fixed_tl
    {0000}{0000}{0000}{0000}{0000}{0000}\@@_sep:
    0,{0000}{0000}{0000}{0000}{0000}{0000}\@@_sep: #1
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[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 \cs{@@_sep:}.  This
%   extended precision is required by other inverse trigonometric
%   functions, to compute things like $\operatorname{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\@@_sep: \s_@@ \@@_chk:w 1#5#6#7\@@_sep:
  {
    \@@_atan_test_o:NwwNwwN
      #2 #3, #4{0000}{0000}\@@_sep:
      #5 #6, #7{0000}{0000}\@@_sep: #1
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[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 $(\operatorname{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~$z$, 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\@@_sep: #4#5,#6\@@_sep:
  {
    \exp_after:wN \@@_atan_combine_o:NwwwwwN
    \exp_after:wN #1
    \int_value:w \@@_int_eval:w
      \if_meaning:w 2 #4
        7 - \@@_int_eval:w
      \fi:
      \if_int_compare:w
          \@@_ep_compare:wwww #2,#3\@@_sep: #5,#6\@@_sep: > \c_zero_int
        3 -
        \exp_after:wN \@@_reverse_args:Nww
      \fi:
      \@@_atan_div:wnwwnw #2,#3\@@_sep: #5,#6\@@_sep:
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[rEXP]{\@@_atan_div:wnwwnw, \@@_atan_near:wwwn}
% \begin{macro}[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 \enquote{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 $\operatorname{atan}\frac{b-a}{a+b}$.  Otherwise, the
%   octant is~$0$ (again, combined with earlier terms) and we wish to
%   compute $\operatorname{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\@@_sep: #4,#5#6\@@_sep:
  {
    \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:
    0
    \@@_ep_div:wwwwn #1,{#2}#3\@@_sep: #4,{#5}#6\@@_sep:
    \@@_atan_auxi:ww
  }
\cs_new:Npn \@@_atan_near:wwwn
    0 \@@_ep_div:wwwwn #1,#2\@@_sep: #3,
  {
    1
    \@@_ep_to_fixed:wwn #1 - #3, #2\@@_sep:
    \@@_atan_near_aux:wwn
  }
\cs_new:Npn \@@_atan_near_aux:wwn #1\@@_sep: #2\@@_sep:
  {
    \@@_fixed_add:wwn #1\@@_sep: #2\@@_sep:
    { \@@_fixed_sub:wwn #2\@@_sep: #1\@@_sep: { \@@_ep_div:wwwwn 0, } 0, }
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}[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\@@_sep:
  { \@@_ep_to_fixed:wwn #1,#2\@@_sep: \@@_atan_auxii:w #1,#2\@@_sep: }
\cs_new:Npn \@@_atan_auxii:w #1\@@_sep:
  {
    \@@_fixed_mul:wwn #1\@@_sep: #1\@@_sep:
    {
      \@@_atan_Taylor_loop:www 39 \@@_sep:
        {0000}{0000}{0000}{0000}{0000}{0000} \@@_sep:
    }
    ! #1\@@_sep:
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[EXP]
%   {\@@_atan_Taylor_loop:www, \@@_atan_Taylor_break:w}
%   We compute the series of $(\operatorname{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 $(\operatorname{atan} z)/z$, and there is a need to
%   clean up all the unnecessary data, end the integer expression
%   computing the octant with a \cs{@@_sep:}, and leave the result~|#2|
%   afterwards.
%    \begin{macrocode}
\cs_new:Npn \@@_atan_Taylor_loop:www #1\@@_sep: #2\@@_sep: #3\@@_sep:
  {
    \if_int_compare:w #1 = - \c_one_int
      \@@_atan_Taylor_break:w
    \fi:
    \exp_after:wN \@@_fixed_div_int:wwN \c_@@_one_fixed_tl #1\@@_sep:
    \@@_rrot:www \@@_fixed_mul_sub_back:wwwn #2\@@_sep: #3\@@_sep:
    {
      \exp_after:wN \@@_atan_Taylor_loop:www
      \int_value:w \@@_int_eval:w #1 - 2 \@@_sep:
    }
    #3\@@_sep:
  }
\cs_new:Npn \@@_atan_Taylor_break:w
    \fi: #1 \@@_fixed_mul_sub_back:wwwn #2\@@_sep: #3 !
  { \fi: \@@_sep: #2 \@@_sep: }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[EXP]
%   {\@@_atan_combine_o:NwwwwwN, \@@_atan_combine_aux:ww}
%   This receives a \meta{sign}, an \meta{octant}, a fixed point value
%   of $(\operatorname{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{\operatorname{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{\operatorname{atan} z}{z}$
%   with~|#6|, the adjusted~$z$.  Otherwise, multiply $|#3|=\frac{\operatorname{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_o:wN}.
%    \begin{macrocode}
\cs_new:Npn \@@_atan_combine_o:NwwwwwN
    #1 #2\@@_sep: #3\@@_sep: #4\@@_sep: #5,#6\@@_sep: #7
  {
    \exp_after:wN \@@_sanitize:Nw
    \exp_after:wN #1
    \int_value:w \@@_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\@@_sep: #6\@@_sep: }
      {
        \@@_fixed_mul:wwn #3\@@_sep: #4\@@_sep:
        {
          \exp_after:wN \@@_atan_combine_aux:ww
          \int_value:w \@@_int_eval:w #2 / 2 \@@_sep: #2\@@_sep:
        }
      }
      { #7 \@@_fixed_to_float_o:wN \@@_fixed_to_float_rad_o:wN }
      #1
  }
\cs_new:Npn \@@_atan_combine_aux:ww #1\@@_sep: #2\@@_sep:
  {
    \@@_fixed_mul_short:wwn
      {7853}{9816}{3397}{4483}{0961}{5661}\@@_sep:
      {#1}{0000}{0000}\@@_sep:
    {
      \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}[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 \enquote{invalid operation}
%   exceptions).
%    \begin{macrocode}
\cs_new:Npn \@@_asin_o:w #1 \s_@@ \@@_chk:w #2#3\@@_sep: @
  {
    \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\@@_sep:
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[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\@@_sep: @
  {
    \if_case:w #2 \exp_stop_f:
      \@@_case_use:nw { \@@_atan_inf_o:NNNw #1 0 4 }
    \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\@@_sep:
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[EXP]{\@@_asin_normal_o:NfwNnnnnw}
%   If the exponent~|#5| is at most $0$, the operand lies
%   within $(-1,1)$ and the operation is permitted: call
%   \cs{@@_asin_auxi_o:NnNww} 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:NnNww}.
%   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\@@_sep:
  {
    \if_int_compare:w #5 < \c_one_int
      \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}\@@_sep:
    \@@_asin_auxi_o:NnNww
      #1 {#3} #4 #5,{#6}{#7}{#8}{#9}{0000}{0000}\@@_sep:
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[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\@@_sep:
  {
    \@@_ep_to_fixed:wwn #4,#5\@@_sep:
    \@@_asin_isqrt:wn
    \@@_ep_mul:wwwwn #4,#5\@@_sep:
    \@@_ep_to_ep:wwN
    \@@_fixed_continue:wn
    { #2 \@@_atan_test_o:NwwNwwN #3 }
    0 1,{1000}{0000}{0000}{0000}{0000}{0000}\@@_sep: #1
  }
\cs_new:Npn \@@_asin_isqrt:wn #1\@@_sep:
  {
    \exp_after:wN \@@_fixed_sub:wwn \c_@@_one_fixed_tl #1\@@_sep:
    {
      \@@_fixed_add_one:wN #1\@@_sep:
      \@@_fixed_continue:wn { \@@_ep_mul:wwwwn 0, } 0,
    }
    \@@_ep_isqrt:wwn
  }
%    \end{macrocode}
% \end{macro}
%
% \subsubsection{Arccosecant and arcsecant}
%
% \begin{macro}[EXP]{\@@_acsc_o:w}
%   Cases are mostly labeled 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\@@_sep: @
  {
    \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\@@_sep:
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[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\@@_sep: @
  {
    \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 4 }
    \else: \@@_case_return_same_o:w
    \fi:
    \s_@@ \@@_chk:w #2 #3\@@_sep:
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[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:NnNww} (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\@@_sep:
  {
    \int_compare:nNnTF {#5} < 1
      {
        \@@_invalid_operation_o:fw {#2}
          \s_@@ \@@_chk:w 1#4{#5}#6\@@_sep:
      }
      {
        \@@_ep_div:wwwwn
          1,{1000}{0000}{0000}{0000}{0000}{0000}\@@_sep:
          #5,#6{0000}{0000}\@@_sep:
        { \@@_asin_auxi_o:NnNww #1 {#3} #4 }
      }
  }
%    \end{macrocode}
% \end{macro}
%
%    \begin{macrocode}
%</code>
%    \end{macrocode}
%
% \end{implementation}
%
% \PrintChanges
%
% \PrintIndex
