% \iffalse meta-comment
%
%% File: l3fparray.dtx
%
% Copyright (C) 2018-2025 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{l3fparray} module\\ Fast global floating point arrays^^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 2025-11-06}
%
% \maketitle
%
% \begin{documentation}
%
% For applications requiring heavy use of floating points, this module
% provides arrays which can be accessed in constant time (contrast
% \pkg{l3seq}, where access time is linear).  The interface is very
% close to that of \pkg{l3intarray}.  The size of the array is fixed and
% must be given at point of initialization
%
% \section{Creating and initializing floating point array variables}
%
% \begin{function}[added = 2018-05-05]{\fparray_new:Nn, \fparray_new:cn}
%   \begin{syntax}
%     \cs{fparray_new:Nn} \meta{fparray~var} \Arg{size}
%   \end{syntax}
%   Evaluates the integer expression \meta{size} and allocates an
%   \meta{fparray var} with that number of (zero)
%   entries.  The variable name should start with |\g_| because
%   assignments are always global.
% \end{function}
%
% \begin{function}[added = 2018-05-05]{\fparray_gzero:N, \fparray_gzero:c}
%   \begin{syntax}
%     \cs{fparray_gzero:N} \meta{fparray~var}
%   \end{syntax}
%   Sets all entries of the \meta{fparray var} to
%   $+0$.  Assignments are always global.
% \end{function}
%
% \section{Adding data to floating point arrays}
%
% \begin{function}[added = 2018-05-05]{\fparray_gset:Nnn, \fparray_gset:cnn}
%   \begin{syntax}
%     \cs{fparray_gset:Nnn} \meta{fparray~var} \Arg{position} \Arg{value}
%   \end{syntax}
%   Stores the result of evaluating the floating point expression
%   \meta{value} into the \meta{fparray var} at the
%   (integer expression) \meta{position}.  If the \meta{position} is not
%   between $1$ and the \cs{fparray_count:N}, an error occurs.
%   Assignments are always global.
% \end{function}
%
% \section{Counting entries in floating point arrays}
%
% \begin{function}[EXP, added = 2018-05-05]{\fparray_count:N, \fparray_count:c}
%   \begin{syntax}
%     \cs{fparray_count:N} \meta{fparray~var}
%   \end{syntax}
%   Expands to the number of entries in the \meta{fparray
%   var}.  This is performed in constant time.
% \end{function}
%
% \section{Using a single entry}
%
% \begin{function}[EXP, added = 2018-05-05]
%   {
%     \fparray_item:Nn, \fparray_item:cn,
%     \fparray_item_to_tl:Nn, \fparray_item_to_tl:cn
%   }
%   \begin{syntax}
%     \cs{fparray_item:Nn} \meta{fparray~var} \Arg{position}
%   \end{syntax}
%   Applies \cs{fp_use:N} or \cs{fp_to_tl:N} (respectively) to the
%   floating point entry stored at the (integer expression)
%   \meta{position} in the \meta{fparray var}.  If the
%   \meta{position} is not between $1$ and the
%   \cs{fparray_count:N} \meta{fparray~var}, an error occurs.
% \end{function}
%
% \section{Floating point array conditional}
%
% \begin{function}[pTF, added = 2024-03-31]
%   {\fparray_if_exist:N, \fparray_if_exist:c}
%   \begin{syntax}
%     \cs{fparray_if_exist_p:N} \meta{fparray~var}
%     \cs{fparray_if_exist:NTF} \meta{fparray~var} \Arg{true code} \Arg{false code}
%   \end{syntax}
%   Tests whether the \meta{fparray~var} is currently defined. This
%   does not check that the \meta{fparray~var} really is a floating
%   point array variable.
% \end{function}
%
% \end{documentation}
%
% \begin{implementation}
%
% \section{\pkg{l3fparray} implementation}
%
%    \begin{macrocode}
%<*code>
%    \end{macrocode}
%
%    \begin{macrocode}
%<@@=fp>
%    \end{macrocode}
%
% In analogy to \pkg{l3intarray} it would make sense to have
% |<@@=fparray>|, but we need direct access to \cs{@@_parse:n} from
% \pkg{l3fp-parse}, and a few other (less crucial) internals of the
% \pkg{l3fp} family.
%
% \subsection{Allocating arrays}
%
% There are somewhat more than $(2^{31}-1)^2$ floating point numbers so
% we store each floating point number as three entries in integer
% arrays.  To avoid having to multiply indices by three or to add 1 etc,
% a floating point array is just a token list consisting of three
% tokens: integer arrays of the same size.
%
% \begin{variable}{\g_@@_array_int}
%   Used to generate unique names for the three integer arrays.
%    \begin{macrocode}
\int_new:N \g_@@_array_int
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\l_@@_array_loop_int}
%   Used to loop in \cs{@@_array_gzero:N}.
%    \begin{macrocode}
\int_new:N \l_@@_array_loop_int
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{\fparray_new:Nn, \fparray_new:cn}
% \begin{macro}{\@@_array_new:nNNN}
%   Build a three-token token list, then define all three tokens to be
%   integer arrays of the same size.  No need to initialize the data:
%   the integer arrays start with zeros, and three zeros denote
%   precisely \cs{c_zero_fp}, as we want.
%    \begin{macrocode}
\cs_new_protected:Npn \fparray_new:Nn #1#2
  {
    \tl_new:N #1
    \prg_replicate:nn { 3 }
      {
        \int_gincr:N \g_@@_array_int
        \exp_args:NNc \tl_gput_right:Nn #1
          { g_@@_array_ \@@_int_to_roman:w \g_@@_array_int _intarray }
      }
    \exp_last_unbraced:Nfo \@@_array_new:nNNNN
      { \int_eval:n {#2} } #1 #1
  }
\cs_generate_variant:Nn \fparray_new:Nn { c }
\cs_new_protected:Npn \@@_array_new:nNNNN #1#2#3#4#5
  {
    \int_compare:nNnTF {#1} < 0
      {
        \msg_error:nnn { kernel } { negative-array-size } {#1}
        \cs_undefine:N #1
        \int_gsub:Nn \g_@@_array_int { 3 }
      }
      {
        \intarray_new:Nn #2 {#1}
        \intarray_new:Nn #3 {#1}
        \intarray_new:Nn #4 {#1}
      }
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}[EXP]{\fparray_count:N, \fparray_count:c}
%   Size of any of the intarrays, here we pick the third.
%    \begin{macrocode}
\cs_new:Npn \fparray_count:N #1
  {
    \exp_after:wN \use_i:nnn
    \exp_after:wN \intarray_count:N #1
  }
\cs_generate_variant:Nn \fparray_count:N { c }
%    \end{macrocode}
% \end{macro}
%
% \subsection{Array items}
%
% \begin{macro}[EXP]{\@@_array_bounds:NNnTF, \@@_array_bounds_error:NNn}
%   See the \pkg{l3intarray} analogue: only names change.
%   The functions \cs{fparray_gset:Nnn} and \cs{fparray_item:Nn} share
%   bounds checking.  The |T| branch is used if |#3| is within bounds of
%   the array |#2|.
%    \begin{macrocode}
\cs_new:Npn \@@_array_bounds:NNnTF #1#2#3#4#5
  {
    \if_int_compare:w 1 > #3 \exp_stop_f:
      \@@_array_bounds_error:NNn #1 #2 {#3}
      #5
    \else:
      \if_int_compare:w #3 > \fparray_count:N #2 \exp_stop_f:
        \@@_array_bounds_error:NNn #1 #2 {#3}
        #5
      \else:
        #4
      \fi:
    \fi:
  }
\cs_new:Npn \@@_array_bounds_error:NNn #1#2#3
  {
    #1 { kernel } { out-of-bounds }
      { \token_to_str:N #2 } {#3} { \fparray_count:N #2 }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\fparray_gset:Nnn, \fparray_gset:cnn}
% \begin{macro}
%   {
%     \@@_array_gset:NNNNww, \@@_array_gset:w,
%     \@@_array_gset_recover:Nw, \@@_array_gset_special:nnNNN,
%     \@@_array_gset_normal:w
%   }
%   Evaluate, then store exponent in one intarray, sign and $8$ digits
%   of mantissa in the next, and $8$ trailing digits in the last.
%    \begin{macrocode}
\cs_new_protected:Npn \fparray_gset:Nnn #1#2#3
  {
    \exp_after:wN \exp_after:wN
    \exp_after:wN \@@_array_gset:NNNNww
    \exp_after:wN #1
    \exp_after:wN #1
    \int_value:w \int_eval:n {#2} \exp_after:wN \@@_sep:
    \exp:w \exp_end_continue_f:w \@@_parse:n {#3}
  }
\cs_generate_variant:Nn \fparray_gset:Nnn { c }
\cs_new_protected:Npn \@@_array_gset:NNNNww #1#2#3#4#5 \@@_sep: #6 \@@_sep:
  {
    \@@_array_bounds:NNnTF \msg_error:nneee #4 {#5}
      {
        \exp_after:wN \@@_change_func_type:NNN
          \@@_use_i_until_s:nw #6 \@@_sep:
          \@@_array_gset:w
          \@@_array_gset_recover:Nw
        #6 \@@_sep: {#5} #1 #2 #3
      }
      { }
  }
\cs_new_protected:Npn \@@_array_gset_recover:Nw #1#2 \@@_sep:
  {
    \@@_error:nffn { unknown-type } { \tl_to_str:n { #2 \@@_sep: } } { } { }
    \exp_after:wN #1 \c_nan_fp
  }
\cs_new_protected:Npn \@@_array_gset:w \s_@@ \@@_chk:w #1#2
  {
    \if_case:w #1 \exp_stop_f:
         \@@_case_return:nw { \@@_array_gset_special:nnNNN {#2} }
    \or: \exp_after:wN \@@_array_gset_normal:w
    \or: \@@_case_return:nw { \@@_array_gset_special:nnNNN { #2 3 } }
    \or: \@@_case_return:nw { \@@_array_gset_special:nnNNN { 1 } }
    \fi:
    \s_@@ \@@_chk:w #1 #2
  }
\cs_new_protected:Npn \@@_array_gset_normal:w
  \s_@@ \@@_chk:w 1 #1 #2 #3#4#5 \@@_sep: #6#7#8#9
  {
    \__kernel_intarray_gset:Nnn #7 {#6} {#2}
    \__kernel_intarray_gset:Nnn #8 {#6}
      { \if_meaning:w 2 #1 3 \else: 1 \fi: #3#4 }
    \__kernel_intarray_gset:Nnn #9 {#6} { 1 \use:nn #5 }
  }
\cs_new_protected:Npn \@@_array_gset_special:nnNNN #1#2#3#4#5
  {
    \__kernel_intarray_gset:Nnn #3 {#2} {#1}
    \__kernel_intarray_gset:Nnn #4 {#2} {0}
    \__kernel_intarray_gset:Nnn #5 {#2} {0}
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\fparray_gzero:N, \fparray_gzero:c}
%    \begin{macrocode}
\cs_new_protected:Npn \fparray_gzero:N #1
  {
    \int_zero:N \l_@@_array_loop_int
    \prg_replicate:nn { \fparray_count:N #1 }
      {
        \int_incr:N \l_@@_array_loop_int
        \exp_after:wN \@@_array_gset_special:nnNNN
        \exp_after:wN 0
        \exp_after:wN \l_@@_array_loop_int
        #1
      }
  }
\cs_generate_variant:Nn \fparray_gzero:N { c }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[EXP]
%   {
%     \fparray_item:Nn,
%     \fparray_item:cn,
%     \fparray_item_to_tl:Nn,
%     \fparray_item_to_tl:cn,
%   }
% \begin{macro}[EXP]
%   {
%     \@@_array_item:NwN,
%     \@@_array_item:NNNnN,
%     \@@_array_item:N,
%     \@@_array_item:w,
%     \@@_array_item_special:w,
%     \@@_array_item_normal:w
%   }
%    \begin{macrocode}
\cs_new:Npn \fparray_item:Nn #1#2
  {
    \exp_after:wN \@@_array_item:NwN
    \exp_after:wN #1
    \int_value:w \int_eval:n {#2} \@@_sep:
    \@@_to_decimal:w
  }
\cs_generate_variant:Nn \fparray_item:Nn { c }
\cs_new:Npn \fparray_item_to_tl:Nn #1#2
  {
    \exp_after:wN \@@_array_item:NwN
    \exp_after:wN #1
    \int_value:w \int_eval:n {#2} \@@_sep:
    \@@_to_tl:w
  }
\cs_generate_variant:Nn \fparray_item_to_tl:Nn { c }
\cs_new:Npn \@@_array_item:NwN #1#2 \@@_sep: #3
  {
    \@@_array_bounds:NNnTF \msg_expandable_error:nnfff #1 {#2}
      { \exp_after:wN \@@_array_item:NNNnN #1 {#2} #3 }
      { \exp_after:wN #3 \c_nan_fp }
  }
\cs_new:Npn \@@_array_item:NNNnN #1#2#3#4
  {
    \exp_after:wN \@@_array_item:N
    \int_value:w \__kernel_intarray_item:Nn #2 {#4} \exp_after:wN \@@_sep:
    \int_value:w \__kernel_intarray_item:Nn #3 {#4} \exp_after:wN \@@_sep:
    \int_value:w \__kernel_intarray_item:Nn #1 {#4} \@@_sep:
  }
\cs_new:Npn \@@_array_item:N #1
  {
    \if_meaning:w 0 #1 \exp_after:wN \@@_array_item_special:w \fi:
    \@@_array_item:w #1
  }
\cs_new:Npn \@@_array_item:w #1 #2#3#4#5 #6 \@@_sep: 1 #7 \@@_sep:
  {
    \exp_after:wN \@@_array_item_normal:w
    \int_value:w \if_meaning:w #1 1 0 \else: 2 \fi: \exp_stop_f:
    #7 \@@_sep: {#2#3#4#5} {#6} \@@_sep:
  }
\cs_new:Npn \@@_array_item_special:w #1 \@@_sep: #2 \@@_sep: #3 \@@_sep: #4
  {
    \exp_after:wN #4
    \exp:w \exp_end_continue_f:w
    \if_case:w #3 \exp_stop_f:
         \exp_after:wN \c_zero_fp
    \or: \exp_after:wN \c_nan_fp
    \or: \exp_after:wN \c_minus_zero_fp
    \or: \exp_after:wN \c_inf_fp
    \else: \exp_after:wN \c_minus_inf_fp
    \fi:
  }
\cs_new:Npn \@@_array_item_normal:w
    #1 #2#3#4#5 #6 \@@_sep: #7 \@@_sep: #8 \@@_sep: #9
  { #9 \s_@@ \@@_chk:w 1 #1 {#8} #7 {#2#3#4#5} {#6} \@@_sep: }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}[pTF]{\fparray_if_exist:N, \fparray_if_exist:c}
%   Copies of the \texttt{cs} functions defined in \pkg{l3basics}.
%    \begin{macrocode}
\prg_new_eq_conditional:NNn \fparray_if_exist:N \cs_if_exist:N
  { TF , T , F , p }
\prg_new_eq_conditional:NNn \fparray_if_exist:c \cs_if_exist:c
  { TF , T , F , p }
%    \end{macrocode}
% \end{macro}
%
%    \begin{macrocode}
%</code>
%    \end{macrocode}
%
% \end{implementation}
%
% \PrintIndex
