% \iffalse meta-comment
%
%% File: l3fp-traps.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-traps} module\\
%   Trapping floating-point exceptions^^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-05-26}
% \maketitle
%
% \begin{documentation}
%
% \end{documentation}
%
% \begin{implementation}
%
% \section{\pkg{l3fp-traps} implementation}
%
%    \begin{macrocode}
%<*code>
%    \end{macrocode}
%
%    \begin{macrocode}
%<@@=fp>
%    \end{macrocode}
%
% Exceptions should be accessed by an \texttt{n}-type argument, among
% \begin{itemize}
% \item \texttt{invalid_operation}
% \item \texttt{division_by_zero}
% \item \texttt{overflow}
% \item \texttt{underflow}
% \item \texttt{inexact} (actually never used).
% \end{itemize}
%
% \subsection{Flags}
%
% \begin{variable}[module = fp]
%   {
%     \l_fp_invalid_operation_flag,
%     \l_fp_division_by_zero_flag,
%     \l_fp_overflow_flag,
%     \l_fp_underflow_flag
%   }
%   Flags to denote exceptions.
%    \begin{macrocode}
\flag_new:N \l_fp_invalid_operation_flag
\flag_new:N \l_fp_division_by_zero_flag
\flag_new:N \l_fp_overflow_flag
\flag_new:N \l_fp_underflow_flag
%    \end{macrocode}
% \end{variable}
%
% \subsection{Traps}
%
% Exceptions can be trapped to obtain custom behavior.  When an invalid
% operation or a division by zero is trapped, the trap receives as
% arguments the result as an |N|-type floating point number, the
% function name (multiple letters for prefix operations, or a single
% symbol for infix operations), and the operand(s).  When an overflow or
% underflow is trapped, the trap receives the resulting overly large or
% small floating point number if it is not too big, otherwise it
% receives $+\infty$.  Currently, the inexact exception is entirely
% ignored.
%
% The behavior when an exception occurs is controlled by the
% definitions of the functions
% \begin{itemize}
% \item \cs{@@_invalid_operation:nnw},
% \item \cs{@@_invalid_operation_o:Nww},
% \item \cs{@@_invalid_operation_tl_o:ff},
% \item \cs{@@_division_by_zero_o:Nnw},
% \item \cs{@@_division_by_zero_o:NNww},
% \item \cs{@@_overflow:w},
% \item \cs{@@_underflow:w}.
% \end{itemize}
% Rather than changing them directly, we provide a user interface as
% \cs{fp_trap:nn} \Arg{exception} \Arg{way of trapping}, where the
% \meta{way of trapping} is one of \texttt{error}, \texttt{flag}, or
% \texttt{none}.
%
% We also provide \cs{@@_invalid_operation_o:nw}, defined in terms of
% \cs{@@_invalid_operation:nnw}.
%
% \begin{macro}{\fp_trap:nn}
%    \begin{macrocode}
\cs_new_protected:Npn \fp_trap:nn #1#2
  {
    \cs_if_exist_use:cF { @@_trap_#1_set_#2: }
      {
        \clist_if_in:nnTF
          { invalid_operation , division_by_zero , overflow , underflow }
          {#1}
          {
            \msg_error:nnee { fp }
              { unknown-fpu-trap-type } {#1} {#2}
          }
          {
            \msg_error:nne
              { fp } { unknown-fpu-exception } {#1}
          }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}
%   {
%     \@@_trap_invalid_operation_set_error: ,
%     \@@_trap_invalid_operation_set_flag:  ,
%     \@@_trap_invalid_operation_set_none:  ,
%     \@@_trap_invalid_operation_set:N      ,
%   }
%   We provide three types of trapping for invalid operations: either
%   produce an error and raise the relevant flag; or only raise the
%   flag; or don't even raise the flag.  In most cases, the function
%   produces as a result its first argument, possibly with
%   post-expansion.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_trap_invalid_operation_set_error:
  { \@@_trap_invalid_operation_set:N \prg_do_nothing: }
\cs_new_protected:Npn \@@_trap_invalid_operation_set_flag:
  { \@@_trap_invalid_operation_set:N \use_none:nnnnn }
\cs_new_protected:Npn \@@_trap_invalid_operation_set_none:
  { \@@_trap_invalid_operation_set:N \use_none:nnnnnnn }
\cs_new_protected:Npn \@@_trap_invalid_operation_set:N #1
  {
    \exp_args:Nno \use:n
      { \cs_set:Npn \@@_invalid_operation:nnw ##1##2##3\@@_sep: }
      {
        #1
        \@@_error:nnfn { invalid } {##2} { \fp_to_tl:n { ##3\@@_sep: } } { }
        \flag_ensure_raised:N \l_fp_invalid_operation_flag
        ##1
      }
    \exp_args:Nno \use:n
      { \cs_set:Npn \@@_invalid_operation_o:Nww ##1##2\@@_sep: ##3\@@_sep: }
      {
        #1
        \@@_error:nffn { invalid-ii }
          { \fp_to_tl:n { ##2\@@_sep: } }
          { \fp_to_tl:n { ##3\@@_sep: } }
          {##1}
        \flag_ensure_raised:N \l_fp_invalid_operation_flag
        \exp_after:wN \c_nan_fp
      }
    \exp_args:Nno \use:n
      { \cs_set:Npn \@@_invalid_operation_tl_o:ff ##1##2 }
      {
        #1
        \@@_error:nffn { invalid } {##1} {##2} { }
        \flag_ensure_raised:N \l_fp_invalid_operation_flag
        \exp_after:wN \c_nan_fp
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}
%   {
%     \@@_trap_division_by_zero_set_error: ,
%     \@@_trap_division_by_zero_set_flag:  ,
%     \@@_trap_division_by_zero_set_none:  ,
%     \@@_trap_division_by_zero_set:N      ,
%   }
%   We provide three types of trapping for invalid operations and
%   division by zero: either produce an error and raise the relevant
%   flag; or only raise the flag; or don't even raise the flag.  In all
%   cases, the function must produce a result, namely its first
%   argument, $\pm\infty$ or \nan{}.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_trap_division_by_zero_set_error:
  { \@@_trap_division_by_zero_set:N \prg_do_nothing: }
\cs_new_protected:Npn \@@_trap_division_by_zero_set_flag:
  { \@@_trap_division_by_zero_set:N \use_none:nnnnn }
\cs_new_protected:Npn \@@_trap_division_by_zero_set_none:
  { \@@_trap_division_by_zero_set:N \use_none:nnnnnnn }
\cs_new_protected:Npn \@@_trap_division_by_zero_set:N #1
  {
    \exp_args:Nno \use:n
      { \cs_set:Npn \@@_division_by_zero_o:Nnw ##1##2##3\@@_sep: }
      {
        #1
        \@@_error:nnfn { zero-div } {##2} { \fp_to_tl:n { ##3\@@_sep: } } { }
        \flag_ensure_raised:N \l_fp_division_by_zero_flag
        \exp_after:wN ##1
      }
    \exp_args:Nno \use:n
      {
        \cs_set:Npn \@@_division_by_zero_o:NNww ##1##2##3\@@_sep: ##4\@@_sep:
      }
      {
        #1
        \@@_error:nffn { zero-div-ii }
          { \fp_to_tl:n { ##3\@@_sep: } }
          { \fp_to_tl:n { ##4\@@_sep: } }
          {##2}
        \flag_ensure_raised:N \l_fp_division_by_zero_flag
        \exp_after:wN ##1
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}
%   {
%     \@@_trap_overflow_set_error: ,
%     \@@_trap_overflow_set_flag:  ,
%     \@@_trap_overflow_set_none:  ,
%     \@@_trap_overflow_set:N      ,
%   }
% \begin{macro}
%   {
%     \@@_trap_underflow_set_error: ,
%     \@@_trap_underflow_set_flag:  ,
%     \@@_trap_underflow_set_none:  ,
%     \@@_trap_underflow_set:N      ,
%   }
% \begin{macro}{\@@_trap_overflow_set:NnNn}
%   Just as for invalid operations and division by zero, the three
%   different behaviors are obtained by feeding \cs{prg_do_nothing:},
%   \cs{use_none:nnnnn} or \cs{use_none:nnnnnnn} to an auxiliary, with a
%   further auxiliary common to overflow and underflow functions.  In
%   most cases, the argument of the \cs{@@_overflow:w} and
%   \cs{@@_underflow:w} functions will be an (almost) normal number
%   (with an exponent outside the allowed range), and the error message
%   thus displays that number together with the result to which it
%   overflowed or underflowed.  For extreme cases such as \texttt{10 **
%     1e9999}, the exponent would be too large for \TeX{}, and
%   \cs{@@_overflow:w} receives $\pm \infty$ (\cs{@@_underflow:w} would
%   receive $\pm 0$); then we cannot do better than simply say an
%   overflow or underflow occurred.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_trap_overflow_set_error:
  { \@@_trap_overflow_set:N \prg_do_nothing: }
\cs_new_protected:Npn \@@_trap_overflow_set_flag:
  { \@@_trap_overflow_set:N \use_none:nnnnn }
\cs_new_protected:Npn \@@_trap_overflow_set_none:
  { \@@_trap_overflow_set:N \use_none:nnnnnnn }
\cs_new_protected:Npn \@@_trap_overflow_set:N #1
  { \@@_trap_overflow_set:NnNn #1 { overflow } \@@_inf_fp:N { inf } }
\cs_new_protected:Npn \@@_trap_underflow_set_error:
  { \@@_trap_underflow_set:N \prg_do_nothing: }
\cs_new_protected:Npn \@@_trap_underflow_set_flag:
  { \@@_trap_underflow_set:N \use_none:nnnnn }
\cs_new_protected:Npn \@@_trap_underflow_set_none:
  { \@@_trap_underflow_set:N \use_none:nnnnnnn }
\cs_new_protected:Npn \@@_trap_underflow_set:N #1
  { \@@_trap_overflow_set:NnNn #1 { underflow } \@@_zero_fp:N { 0 } }
\cs_new_protected:Npn \@@_trap_overflow_set:NnNn #1#2#3#4
  {
    \exp_args:Nno \use:n
      { \cs_set:cpn { @@_ #2 :w } \s_@@ \@@_chk:w ##1##2##3\@@_sep: }
      {
        #1
        \@@_error:nffn
          { flow \if_meaning:w 1 ##1 -to \fi: }
          { \fp_to_tl:n { \s_@@ \@@_chk:w ##1##2##3\@@_sep: } }
          { \token_if_eq_meaning:NNF 0 ##2 { - } #4 }
          {#2}
        \flag_ensure_raised:c { l_fp_#2_flag }
        #3 ##2
      }
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macro}[EXP]
%   {
%     \@@_invalid_operation:nnw, \@@_invalid_operation_o:Nww,
%     \@@_invalid_operation_tl_o:ff,
%     \@@_division_by_zero_o:Nnw, \@@_division_by_zero_o:NNww,
%     \@@_overflow:w , \@@_underflow:w
%   }
%   Initialize the control sequences (to log properly their
%   existence).  Then set invalid operations to trigger an error, and
%   division by zero, overflow, and underflow to act silently on their
%   flag.
%    \begin{macrocode}
\cs_new:Npn \@@_invalid_operation:nnw #1#2#3\@@_sep: { }
\cs_new:Npn \@@_invalid_operation_o:Nww #1#2\@@_sep: #3\@@_sep: { }
\cs_new:Npn \@@_invalid_operation_tl_o:ff #1 #2 { }
\cs_new:Npn \@@_division_by_zero_o:Nnw #1#2#3\@@_sep: { }
\cs_new:Npn \@@_division_by_zero_o:NNww #1#2#3\@@_sep: #4\@@_sep: { }
\cs_new:Npn \@@_overflow:w { }
\cs_new:Npn \@@_underflow:w { }
\fp_trap:nn { invalid_operation } { error }
\fp_trap:nn { division_by_zero } { flag }
\fp_trap:nn { overflow } { flag }
\fp_trap:nn { underflow } { flag }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[EXP]
%   {\@@_invalid_operation_o:nw, \@@_invalid_operation_o:fw}
%   Convenient short-hands for returning \cs{c_nan_fp} for a unary or
%   binary operation, and expanding after.
%    \begin{macrocode}
\cs_new:Npn \@@_invalid_operation_o:nw
  { \@@_invalid_operation:nnw { \exp_after:wN \c_nan_fp } }
\cs_generate_variant:Nn \@@_invalid_operation_o:nw { f }
%    \end{macrocode}
% \end{macro}
%
% \subsection{Errors}
%
% \begin{macro}[EXP]{\@@_error:nnnn, \@@_error:nnfn, \@@_error:nffn, \@@_error:nfff}
%    \begin{macrocode}
\cs_new:Npn \@@_error:nnnn
  { \msg_expandable_error:nnnnn { fp } }
\cs_generate_variant:Nn \@@_error:nnnn { nnf, nff , nfff }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[EXP]{\@@_error_num_args:nnnn, \@@_error_num_args:ffff}
%    \begin{macrocode}
\cs_new:Npn \@@_error_num_args:nnnn #1#2#3#4
  {
    \int_compare:nNnTF {#2} = {#3}
      { \msg_expandable_error:nnnnn { fp } { num-args-eq } {#1} {#2} {#4} }
      { \msg_expandable_error:nnnnnn { fp } { num-args } {#1} {#2} {#3} {#4} }
  }
\cs_generate_variant:Nn \@@_error_num_args:nnnn { ffff }
\msg_new:nnn { fp } { num-args-eq }
  { #1()~needs~#2~arguments,~got~#3. }
\msg_new:nnn { fp } { num-args }
  { #1()~needs~#2~to~#3~arguments,~got~#4. }
%    \end{macrocode}
% \end{macro}
%
% \subsection{Messages}
%
% Some messages.
%    \begin{macrocode}
\msg_new:nnnn { fp } { unknown-fpu-exception }
  {
    The~FPU~exception~'#1'~is~not~known:~
    that~trap~will~never~be~triggered.
  }
  {
    The~only~exceptions~to~which~traps~can~be~attached~are \\
    \iow_indent:n
      {
        * ~ invalid_operation \\
        * ~ division_by_zero \\
        * ~ overflow \\
        * ~ underflow
      }
  }
\msg_new:nnnn { fp } { unknown-fpu-trap-type }
  { The~FPU~trap~type~'#2'~is~not~known. }
  {
    The~trap~type~must~be~one~of \\
    \iow_indent:n
      {
        * ~ error \\
        * ~ flag \\
        * ~ none
      }
  }
\msg_new:nnn { fp } { flow }
  { An ~ #3 ~ occurred. }
\msg_new:nnn { fp } { flow-to }
  { #1 ~ #3 ed ~ to ~ #2 . }
\msg_new:nnn { fp } { zero-div }
  { Division~by~zero~in~ #1 (#2) }
\msg_new:nnn { fp } { zero-div-ii }
  { Division~by~zero~in~ (#1) #3 (#2) }
\msg_new:nnn { fp } { invalid }
  { Invalid~operation~ #1 (#2) }
\msg_new:nnn { fp } { invalid-ii }
  { Invalid~operation~ (#1) #3 (#2) }
\msg_new:nnn { fp } { unknown-type }
  { Unknown~type~for~'#1' }
%    \end{macrocode}
%
%    \begin{macrocode}
%</code>
%    \end{macrocode}
%
% \end{implementation}
%
% \PrintChanges
%
% \PrintIndex
