% \iffalse meta-comment % %% File: l3fp-traps.dtx % % Copyright (C) 2011-2024 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} % % \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 2024-03-14} % \maketitle % % \begin{documentation} % % \end{documentation} % % \begin{implementation} % % \section{\pkg{l3fp-traps} implementation} % % \begin{macrocode} %<*package> % \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 behaviour. 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 behaviour 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; } { #1 \@@_error:nnfn { invalid } {##2} { \fp_to_tl:n { ##3; } } { } \flag_ensure_raised:N \l_fp_invalid_operation_flag ##1 } \exp_args:Nno \use:n { \cs_set:Npn \@@_invalid_operation_o:Nww ##1##2; ##3; } { #1 \@@_error:nffn { invalid-ii } { \fp_to_tl:n { ##2; } } { \fp_to_tl:n { ##3; } } {##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; } { #1 \@@_error:nnfn { zero-div } {##2} { \fp_to_tl:n { ##3; } } { } \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; ##4; } { #1 \@@_error:nffn { zero-div-ii } { \fp_to_tl:n { ##3; } } { \fp_to_tl:n { ##4; } } {##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 behaviours 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; } { #1 \@@_error:nffn { flow \if_meaning:w 1 ##1 -to \fi: } { \fp_to_tl:n { \s_@@ \@@_chk:w ##1##2##3; } } { \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; { } \cs_new:Npn \@@_invalid_operation_o:Nww #1#2; #3; { } \cs_new:Npn \@@_invalid_operation_tl_o:ff #1 #2 { } \cs_new:Npn \@@_division_by_zero_o:Nnw #1#2#3; { } \cs_new:Npn \@@_division_by_zero_o:NNww #1#2#3; #4; { } \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} % % \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} % % \end{macrocode} % % \end{implementation} % % \PrintChanges % % \PrintIndex