summaryrefslogtreecommitdiff
path: root/macros/latex/required/l3kernel/l3fp-logic.dtx
diff options
context:
space:
mode:
Diffstat (limited to 'macros/latex/required/l3kernel/l3fp-logic.dtx')
-rw-r--r--macros/latex/required/l3kernel/l3fp-logic.dtx750
1 files changed, 750 insertions, 0 deletions
diff --git a/macros/latex/required/l3kernel/l3fp-logic.dtx b/macros/latex/required/l3kernel/l3fp-logic.dtx
new file mode 100644
index 0000000000..9df6a4730a
--- /dev/null
+++ b/macros/latex/required/l3kernel/l3fp-logic.dtx
@@ -0,0 +1,750 @@
+% \iffalse meta-comment
+%
+%% File: l3fp-logic.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}
+%</driver>
+% \fi
+%
+% \title{^^A
+% The \pkg{l3fp-logic} module\\
+% Floating point conditionals^^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-04-11}
+%
+% \maketitle
+%
+% \begin{documentation}
+%
+% \end{documentation}
+%
+% \begin{implementation}
+%
+% \section{\pkg{l3fp-logic} implementation}
+%
+% \begin{macrocode}
+%<*package>
+% \end{macrocode}
+%
+% \begin{macrocode}
+%<@@=fp>
+% \end{macrocode}
+%
+% \begin{macro}[EXP]{\@@_parse_word_max:N , \@@_parse_word_min:N}
+% Those functions may receive a variable number of arguments.
+% \begin{macrocode}
+\cs_new:Npn \@@_parse_word_max:N
+ { \@@_parse_function:NNN \@@_minmax_o:Nw 2 }
+\cs_new:Npn \@@_parse_word_min:N
+ { \@@_parse_function:NNN \@@_minmax_o:Nw 0 }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{Syntax of internal functions}
+%
+% \begin{itemize}
+% \item \cs{@@_compare_npos:nwnw} \Arg{expo_1} \meta{body_1} |;|
+% \Arg{expo_2} \meta{body_2} |;|
+% \item \cs{@@_minmax_o:Nw} \meta{sign} \meta{floating point array}
+% \item \cs{@@_not_o:w} |?| \meta{floating point array} (with one floating point number only)
+% \item \cs{@@_&_o:ww} \meta{floating point} \meta{floating point}
+% \item \cs{@@_|_o:ww} \meta{floating point} \meta{floating point}
+% \item \cs{@@_ternary:NwwN}, \cs{@@_ternary_auxi:NwwN},
+% \cs{@@_ternary_auxii:NwwN} have to be understood.
+% \end{itemize}
+%
+% \subsection{Tests}
+%
+% \begin{macro}[pTF]{\fp_if_exist:N, \fp_if_exist:c}
+% Copies of the \texttt{cs} functions defined in \pkg{l3basics}.
+% \begin{macrocode}
+\prg_new_eq_conditional:NNn \fp_if_exist:N \cs_if_exist:N { TF , T , F , p }
+\prg_new_eq_conditional:NNn \fp_if_exist:c \cs_if_exist:c { TF , T , F , p }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[pTF]{\fp_if_nan:n}
+% Evaluate and check if the result is a floating point of the same
+% kind as \nan{}.
+% \begin{macrocode}
+\prg_new_conditional:Npnn \fp_if_nan:n #1 { TF , T , F , p }
+ {
+ \if:w 3 \exp_last_unbraced:Nf \@@_kind:w { \@@_parse:n {#1} }
+ \prg_return_true:
+ \else:
+ \prg_return_false:
+ \fi:
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{Comparison}
+%
+% \begin{macro}[pTF, EXP]{\fp_compare:n}
+% \begin{macro}[EXP]{\@@_compare_return:w}
+% Within floating point expressions, comparison operators are treated
+% as operations, so we evaluate |#1|, then compare with $\pm 0$.
+% Tuples are \texttt{true}.
+% \begin{macrocode}
+\prg_new_conditional:Npnn \fp_compare:n #1 { p , T , F , TF }
+ {
+ \exp_after:wN \@@_compare_return:w
+ \exp:w \exp_end_continue_f:w \@@_parse:n {#1}
+ }
+\cs_new:Npn \@@_compare_return:w #1#2#3;
+ {
+ \if_charcode:w 0
+ \@@_if_type_fp:NTwFw
+ #1 { \@@_use_i_delimit_by_s_stop:nw #3 \s_@@_stop }
+ \s_@@ 1 \s_@@_stop
+ \prg_return_false:
+ \else:
+ \prg_return_true:
+ \fi:
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[pTF, EXP]{\fp_compare:nNn}
+% \begin{macro}[EXP]{\@@_compare_aux:wn}
+% Evaluate |#1| and |#3|, using an auxiliary to expand both, and feed
+% the two floating point numbers swapped to \cs{@@_compare_back_any:ww},
+% defined below. Compare the result with |`#2-`=|, which is $-1$ for
+% |<|, $0$ for |=|, $1$ for |>| and $2$ for |?|.
+% \begin{macrocode}
+\prg_new_conditional:Npnn \fp_compare:nNn #1#2#3 { p , T , F , TF }
+ {
+ \if_int_compare:w
+ \exp_after:wN \@@_compare_aux:wn
+ \exp:w \exp_end_continue_f:w \@@_parse:n {#1} {#3}
+ = \@@_int_eval:w `#2 - `= \@@_int_eval_end:
+ \prg_return_true:
+ \else:
+ \prg_return_false:
+ \fi:
+ }
+\cs_new:Npn \@@_compare_aux:wn #1; #2
+ {
+ \exp_after:wN \@@_compare_back_any:ww
+ \exp:w \exp_end_continue_f:w \@@_parse:n {#2} #1;
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_compare_back:ww, \@@_bcmp:ww, \@@_compare_back_any:ww, \@@_compare_nan:w}
+% \begin{quote}
+% \cs{@@_compare_back_any:ww} \meta{y} |;| \meta{x} |;|
+% \end{quote}
+% Expands (in the same way as \cs{int_eval:n}) to $-1$ if $x<y$, $0$
+% if $x=y$, $1$ if $x>y$, and $2$ otherwise (denoted as $x?y$). If
+% either operand is \texttt{nan}, stop the comparison with
+% \cs{@@_compare_nan:w} returning $2$. If $x$ is negative, swap the
+% outputs $1$ and $-1$ (\emph{i.e.}, $>$ and $<$); we can henceforth
+% assume that $x\geq 0$. If $y\geq 0$, and they have the same type,
+% either they are normal and we compare them with
+% \cs{@@_compare_npos:nwnw}, or they are equal. If $y\geq 0$, but of
+% a different type, the highest type is a larger number. Finally, if
+% $y\leq 0$, then $x>y$, unless both are zero.
+% \begin{macrocode}
+\cs_new:Npn \@@_compare_back:ww #1#2; #3#4;
+ {
+ \cs:w
+ @@
+ \@@_type_from_scan:N #1
+ _bcmp
+ \@@_type_from_scan:N #3
+ :ww
+ \cs_end:
+ #1#2; #3#4;
+ }
+\cs_new:Npn \@@_compare_back_any:ww #1#2; #3
+ {
+ \@@_if_type_fp:NTwFw
+ #1 { \@@_if_type_fp:NTwFw #3 \use_i:nn \s_@@ \use_ii:nn \s_@@_stop }
+ \s_@@ \use_ii:nn \s_@@_stop
+ \@@_compare_back:ww
+ {
+ \cs:w
+ @@
+ \@@_type_from_scan:N #1
+ _compare_back
+ \@@_type_from_scan:N #3
+ :ww
+ \cs_end:
+ }
+ #1#2 ; #3
+ }
+\cs_new:Npn \@@_bcmp:ww
+ \s_@@ \@@_chk:w #1 #2 #3;
+ \s_@@ \@@_chk:w #4 #5 #6;
+ {
+ \int_value:w
+ \if_meaning:w 3 #1 \exp_after:wN \@@_compare_nan:w \fi:
+ \if_meaning:w 3 #4 \exp_after:wN \@@_compare_nan:w \fi:
+ \if_meaning:w 2 #5 - \fi:
+ \if_meaning:w #2 #5
+ \if_meaning:w #1 #4
+ \if_meaning:w 1 #1
+ \@@_compare_npos:nwnw #6; #3;
+ \else:
+ 0
+ \fi:
+ \else:
+ \if_int_compare:w #4 < #1 - \fi: 1
+ \fi:
+ \else:
+ \if_int_compare:w #1#4 = \c_zero_int
+ 0
+ \else:
+ 1
+ \fi:
+ \fi:
+ \exp_stop_f:
+ }
+\cs_new:Npn \@@_compare_nan:w #1 \fi: \exp_stop_f: { 2 \exp_stop_f: }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_compare_back_tuple:ww, \@@_tuple_compare_back:ww, \@@_tuple_compare_back_tuple:ww}
+% \begin{macro}[EXP]{\@@_tuple_compare_back_loop:w}
+% Tuple and floating point numbers are not comparable so return $2$ in
+% mixed cases or when tuples have a different number of items.
+% Otherwise compare pairs of items with \cs{@@_compare_back_any:ww}
+% and if any don't match return~$2$ (as \cs{int_value:w} |02|
+% \cs{exp_stop_f:}).
+% \begin{macrocode}
+\cs_new:Npn \@@_compare_back_tuple:ww #1; #2; { 2 }
+\cs_new:Npn \@@_tuple_compare_back:ww #1; #2; { 2 }
+\cs_new:Npn \@@_tuple_compare_back_tuple:ww
+ \s_@@_tuple \@@_tuple_chk:w #1;
+ \s_@@_tuple \@@_tuple_chk:w #2;
+ {
+ \int_compare:nNnTF { \@@_array_count:n {#1} } =
+ { \@@_array_count:n {#2} }
+ {
+ \int_value:w 0
+ \@@_tuple_compare_back_loop:w
+ #1 { \s_@@ \prg_break: } ; @
+ #2 { \s_@@ \prg_break: } ;
+ \prg_break_point:
+ \exp_stop_f:
+ }
+ { 2 }
+ }
+\cs_new:Npn \@@_tuple_compare_back_loop:w #1#2 ; #3 @ #4#5 ;
+ {
+ \use_none:n #1
+ \use_none:n #4
+ \if_int_compare:w
+ \@@_compare_back_any:ww #1 #2 ; #4 #5 ; = \c_zero_int
+ \else:
+ 2 \exp_after:wN \prg_break:
+ \fi:
+ \@@_tuple_compare_back_loop:w #3 @
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_compare_npos:nwnw}
+% \begin{macro}[EXP]{\@@_compare_significand:nnnnnnnn}
+% \begin{quote}
+% \cs{@@_compare_npos:nwnw}
+% \Arg{expo_1} \meta{body_1} |;|
+% \Arg{expo_2} \meta{body_2} |;|
+% \end{quote}
+% Within an \cs{int_value:w} \ldots{} \cs{exp_stop_f:} construction,
+% this expands to $0$ if the two numbers are equal, $-1$ if the first
+% is smaller, and $1$ if the first is bigger. First compare the
+% exponents: the larger one denotes the larger number. If they are
+% equal, we must compare significands. If both the first $8$ digits and
+% the next $8$ digits coincide, the numbers are equal. If only the
+% first $8$ digits coincide, the next $8$ decide. Otherwise, the
+% first $8$ digits are compared.
+% \begin{macrocode}
+\cs_new:Npn \@@_compare_npos:nwnw #1#2; #3#4;
+ {
+ \if_int_compare:w #1 = #3 \exp_stop_f:
+ \@@_compare_significand:nnnnnnnn #2 #4
+ \else:
+ \if_int_compare:w #1 < #3 - \fi: 1
+ \fi:
+ }
+\cs_new:Npn \@@_compare_significand:nnnnnnnn #1#2#3#4#5#6#7#8
+ {
+ \if_int_compare:w #1#2 = #5#6 \exp_stop_f:
+ \if_int_compare:w #3#4 = #7#8 \exp_stop_f:
+ 0
+ \else:
+ \if_int_compare:w #3#4 < #7#8 - \fi: 1
+ \fi:
+ \else:
+ \if_int_compare:w #1#2 < #5#6 - \fi: 1
+ \fi:
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Floating point expression loops}
+%
+% \begin{macro}[rEXP]
+% {
+% \fp_do_until:nn, \fp_do_while:nn,
+% \fp_until_do:nn, \fp_while_do:nn
+% }
+% These are quite easy given the above functions. The |do_until| and
+% |do_while| versions execute the body, then test. The |until_do| and
+% |while_do| do it the other way round.
+% \begin{macrocode}
+\cs_new:Npn \fp_do_until:nn #1#2
+ {
+ #2
+ \fp_compare:nF {#1}
+ { \fp_do_until:nn {#1} {#2} }
+ }
+\cs_new:Npn \fp_do_while:nn #1#2
+ {
+ #2
+ \fp_compare:nT {#1}
+ { \fp_do_while:nn {#1} {#2} }
+ }
+\cs_new:Npn \fp_until_do:nn #1#2
+ {
+ \fp_compare:nF {#1}
+ {
+ #2
+ \fp_until_do:nn {#1} {#2}
+ }
+ }
+\cs_new:Npn \fp_while_do:nn #1#2
+ {
+ \fp_compare:nT {#1}
+ {
+ #2
+ \fp_while_do:nn {#1} {#2}
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[rEXP]
+% {
+% \fp_do_until:nNnn, \fp_do_while:nNnn,
+% \fp_until_do:nNnn, \fp_while_do:nNnn
+% }
+% As above but not using the |nNn| syntax.
+% \begin{macrocode}
+\cs_new:Npn \fp_do_until:nNnn #1#2#3#4
+ {
+ #4
+ \fp_compare:nNnF {#1} #2 {#3}
+ { \fp_do_until:nNnn {#1} #2 {#3} {#4} }
+ }
+\cs_new:Npn \fp_do_while:nNnn #1#2#3#4
+ {
+ #4
+ \fp_compare:nNnT {#1} #2 {#3}
+ { \fp_do_while:nNnn {#1} #2 {#3} {#4} }
+ }
+\cs_new:Npn \fp_until_do:nNnn #1#2#3#4
+ {
+ \fp_compare:nNnF {#1} #2 {#3}
+ {
+ #4
+ \fp_until_do:nNnn {#1} #2 {#3} {#4}
+ }
+ }
+\cs_new:Npn \fp_while_do:nNnn #1#2#3#4
+ {
+ \fp_compare:nNnT {#1} #2 {#3}
+ {
+ #4
+ \fp_while_do:nNnn {#1} #2 {#3} {#4}
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\fp_step_function:nnnN, \fp_step_function:nnnc}
+% \begin{macro}[EXP]{\@@_step:wwwN, \@@_step_fp:wwwN}
+% \begin{macro}[EXP]{\@@_step:NnnnnN, \@@_step:NfnnnN}
+% The approach here is somewhat similar to
+% \cs{int_step_function:nnnN}. There are two subtleties: we use the
+% internal parser \cs{@@_parse:n} to avoid converting back and forth
+% from the internal representation; and (due to rounding) even a
+% non-zero step does not guarantee that the loop counter increases.
+% \begin{macrocode}
+\cs_new:Npn \fp_step_function:nnnN #1#2#3
+ {
+ \exp_after:wN \@@_step:wwwN
+ \exp:w \exp_end_continue_f:w \@@_parse_o:n {#1}
+ \exp:w \exp_end_continue_f:w \@@_parse_o:n {#2}
+ \exp:w \exp_end_continue_f:w \@@_parse:n {#3}
+ }
+\cs_generate_variant:Nn \fp_step_function:nnnN { nnnc }
+% \end{macrocode}
+% Only floating point numbers (not tuples) are allowed arguments.
+% Only \enquote{normal} floating points (not $\pm 0$,
+% $\pm\texttt{inf}$, \texttt{nan}) can be used as step; if positive,
+% call \cs{@@_step:NnnnnN} with argument |>| otherwise~|<|. This
+% function has one more argument than its integer counterpart, namely
+% the previous value, to catch the case where the loop has made no
+% progress. Conversion to decimal is done just before calling the
+% user's function.
+% \begin{macrocode}
+\cs_new:Npn \@@_step:wwwN #1#2; #3#4; #5#6; #7
+ {
+ \@@_if_type_fp:NTwFw #1 { } \s_@@ \prg_break: \s_@@_stop
+ \@@_if_type_fp:NTwFw #3 { } \s_@@ \prg_break: \s_@@_stop
+ \@@_if_type_fp:NTwFw #5 { } \s_@@ \prg_break: \s_@@_stop
+ \use_i:nnnn { \@@_step_fp:wwwN #1#2; #3#4; #5#6; #7 }
+ \prg_break_point:
+ \use:n
+ {
+ \@@_error:nfff { step-tuple } { \fp_to_tl:n { #1#2 ; } }
+ { \fp_to_tl:n { #3#4 ; } } { \fp_to_tl:n { #5#6 ; } }
+ }
+ }
+\cs_new:Npn \@@_step_fp:wwwN #1 ; \s_@@ \@@_chk:w #2#3#4 ; #5; #6
+ {
+ \token_if_eq_meaning:NNTF #2 1
+ {
+ \token_if_eq_meaning:NNTF #3 0
+ { \@@_step:NnnnnN > }
+ { \@@_step:NnnnnN < }
+ }
+ {
+ \token_if_eq_meaning:NNTF #2 0
+ {
+ \msg_expandable_error:nnn { kernel }
+ { zero-step } {#6}
+ }
+ {
+ \@@_error:nnfn { bad-step } { }
+ { \fp_to_tl:n { \s_@@ \@@_chk:w #2#3#4 ; } } {#6}
+ }
+ \use_none:nnnnn
+ }
+ { #1 ; } { \c_nan_fp } { \s_@@ \@@_chk:w #2#3#4 ; } { #5 ; } #6
+ }
+\cs_new:Npn \@@_step:NnnnnN #1#2#3#4#5#6
+ {
+ \fp_compare:nNnTF {#2} = {#3}
+ {
+ \@@_error:nffn { tiny-step }
+ { \fp_to_tl:n {#3} } { \fp_to_tl:n {#4} } {#6}
+ }
+ {
+ \fp_compare:nNnF {#2} #1 {#5}
+ {
+ \exp_args:Nf #6 { \@@_to_decimal_dispatch:w #2 }
+ \@@_step:NfnnnN
+ #1 { \@@_parse:n { #2 + #4 } } {#2} {#4} {#5} #6
+ }
+ }
+ }
+\cs_generate_variant:Nn \@@_step:NnnnnN { Nf }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\fp_step_inline:nnnn, \fp_step_variable:nnnNn}
+% \begin{macro}{\@@_step:NNnnnn}
+% As for \cs{int_step_inline:nnnn}, create a global function and apply it,
+% following up with a break point.
+% \begin{macrocode}
+\cs_new_protected:Npn \fp_step_inline:nnnn
+ {
+ \int_gincr:N \g__kernel_prg_map_int
+ \exp_args:NNc \@@_step:NNnnnn
+ \cs_gset_protected:Npn
+ { @@_map_ \int_use:N \g__kernel_prg_map_int :w }
+ }
+\cs_new_protected:Npn \fp_step_variable:nnnNn #1#2#3#4#5
+ {
+ \int_gincr:N \g__kernel_prg_map_int
+ \exp_args:NNc \@@_step:NNnnnn
+ \cs_gset_protected:Npe
+ { @@_map_ \int_use:N \g__kernel_prg_map_int :w }
+ {#1} {#2} {#3}
+ {
+ \tl_set:Nn \exp_not:N #4 {##1}
+ \exp_not:n {#5}
+ }
+ }
+\cs_new_protected:Npn \@@_step:NNnnnn #1#2#3#4#5#6
+ {
+ #1 #2 ##1 {#6}
+ \fp_step_function:nnnN {#3} {#4} {#5} #2
+ \prg_break_point:Nn \scan_stop: { \int_gdecr:N \g__kernel_prg_map_int }
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macrocode}
+\msg_new:nnn { fp } { step-tuple }
+ { Tuple~argument~in~fp_step_...~{#1}{#2}{#3}. }
+\msg_new:nnn { fp } { bad-step }
+ { Invalid~step~size~#2~for~function~#3. }
+\msg_new:nnn { fp } { tiny-step }
+ { Tiny~step~size~(#1+#2=#1)~for~function~#3. }
+% \end{macrocode}
+%
+% \subsection{Extrema}
+%
+% \begin{macro}[EXP]{\@@_minmax_o:Nw, \@@_minmax_aux_o:Nw}
+% First check all operands are floating point numbers.
+% The argument~|#1| is $2$~to find the maximum of an array~|#2| of
+% floating point numbers, and $0$~to find the minimum. We read
+% numbers sequentially, keeping track of the largest (smallest) number
+% found so far. If numbers are equal (for instance~$\pm0$), the first
+% is kept. We append $-\infty$ ($\infty$), for the case of an empty
+% array. Since no number is smaller (larger) than that, this
+% additional item only affects the maximum (minimum) in the case of
+% |max()| and |min()| with no argument. The weird
+% fp-like trailing marker breaks the loop correctly: see the precise
+% definition of \cs{@@_minmax_loop:Nww}.
+% \begin{macrocode}
+\cs_new:Npn \@@_minmax_o:Nw #1
+ {
+ \@@_parse_function_all_fp_o:fnw
+ { \token_if_eq_meaning:NNTF 0 #1 { min } { max } }
+ { \@@_minmax_aux_o:Nw #1 }
+ }
+\cs_new:Npn \@@_minmax_aux_o:Nw #1#2 @
+ {
+ \if_meaning:w 0 #1
+ \exp_after:wN \@@_minmax_loop:Nww \exp_after:wN +
+ \else:
+ \exp_after:wN \@@_minmax_loop:Nww \exp_after:wN -
+ \fi:
+ #2
+ \s_@@ \@@_chk:w 2 #1 \s_@@_exact ;
+ \s_@@ \@@_chk:w { 3 \@@_minmax_break_o:w } ;
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_minmax_loop:Nww}
+% The first argument is $-$ or $+$ to denote the case where the
+% currently largest (smallest) number found (first floating point
+% argument) should be replaced by the new number (second floating
+% point argument). If the new number is \texttt{nan}, keep that as
+% the extremum, unless that extremum is already a \texttt{nan}.
+% Otherwise, compare the two numbers. If the new number is larger (in
+% the case of |max|) or smaller (in the case of |min|), the test
+% yields \texttt{true}, and we keep the second number as a new
+% maximum; otherwise we keep the first number. Then loop.
+% \begin{macrocode}
+\cs_new:Npn \@@_minmax_loop:Nww
+ #1 \s_@@ \@@_chk:w #2#3; \s_@@ \@@_chk:w #4#5;
+ {
+ \if_meaning:w 3 #4
+ \if_meaning:w 3 #2
+ \@@_minmax_auxi:ww
+ \else:
+ \@@_minmax_auxii:ww
+ \fi:
+ \else:
+ \if_int_compare:w
+ \@@_compare_back:ww
+ \s_@@ \@@_chk:w #4#5;
+ \s_@@ \@@_chk:w #2#3;
+ = #1 1 \exp_stop_f:
+ \@@_minmax_auxii:ww
+ \else:
+ \@@_minmax_auxi:ww
+ \fi:
+ \fi:
+ \@@_minmax_loop:Nww #1
+ \s_@@ \@@_chk:w #2#3;
+ \s_@@ \@@_chk:w #4#5;
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_minmax_auxi:ww, \@@_minmax_auxii:ww}
+% Keep the first/second number, and remove the other.
+% \begin{macrocode}
+\cs_new:Npn \@@_minmax_auxi:ww #1 \fi: \fi: #2 \s_@@ #3 ; \s_@@ #4;
+ { \fi: \fi: #2 \s_@@ #3 ; }
+\cs_new:Npn \@@_minmax_auxii:ww #1 \fi: \fi: #2 \s_@@ #3 ;
+ { \fi: \fi: #2 }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_minmax_break_o:w}
+% This function is called from within an \cs{if_meaning:w} test. Skip
+% to the end of the tests, close the current test with \cs{fi:}, clean
+% up, and return the appropriate number with one post-expansion.
+% \begin{macrocode}
+\cs_new:Npn \@@_minmax_break_o:w #1 \fi: \fi: #2 \s_@@ #3; #4;
+ { \fi: \@@_exp_after_o:w \s_@@ #3; }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{Boolean operations}
+%
+% \begin{macro}[EXP]{\@@_not_o:w, \@@_tuple_not_o:w}
+% Return \texttt{true} or \texttt{false}, with two expansions, one to
+% exit the conditional, and one to please \pkg{l3fp-parse}. The first
+% argument is provided by \pkg{l3fp-parse} and is ignored.
+% \begin{macrocode}
+\cs_new:Npn \@@_not_o:w #1 \s_@@ \@@_chk:w #2#3; @
+ {
+ \if_meaning:w 0 #2
+ \exp_after:wN \exp_after:wN \exp_after:wN \c_one_fp
+ \else:
+ \exp_after:wN \exp_after:wN \exp_after:wN \c_zero_fp
+ \fi:
+ }
+\cs_new:Npn \@@_tuple_not_o:w #1 @ { \exp_after:wN \c_zero_fp }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_&_o:ww, \@@_tuple_&_o:ww, \@@_&_tuple_o:ww, \@@_tuple_&_tuple_o:ww}
+% \begin{macro}[EXP]{\@@_|_o:ww, \@@_tuple_|_o:ww, \@@_|_tuple_o:ww, \@@_tuple_|_tuple_o:ww}
+% \begin{macro}[EXP]{\@@_and_return:wNw}
+% For \texttt{and}, if the first number is zero, return it (with the
+% same sign). Otherwise, return the second one. For \texttt{or}, the
+% logic is reversed: if the first number is non-zero, return it,
+% otherwise return the second number: we achieve that by hi-jacking
+% \cs{@@_&_o:ww}, inserting an extra argument, \cs{else:}, before
+% \cs{s_@@}. In all cases, expand after the floating point number.
+% \begin{macrocode}
+\group_begin:
+ \char_set_catcode_letter:N &
+ \char_set_catcode_letter:N |
+ \cs_new:Npn \@@_&_o:ww #1 \s_@@ \@@_chk:w #2#3;
+ {
+ \if_meaning:w 0 #2 #1
+ \@@_and_return:wNw \s_@@ \@@_chk:w #2#3;
+ \fi:
+ \@@_exp_after_o:w
+ }
+ \cs_new:Npn \@@_&_tuple_o:ww #1 \s_@@ \@@_chk:w #2#3;
+ {
+ \if_meaning:w 0 #2 #1
+ \@@_and_return:wNw \s_@@ \@@_chk:w #2#3;
+ \fi:
+ \@@_exp_after_tuple_o:w
+ }
+ \cs_new:Npn \@@_tuple_&_o:ww #1; { \@@_exp_after_o:w }
+ \cs_new:Npn \@@_tuple_&_tuple_o:ww #1; { \@@_exp_after_tuple_o:w }
+ \cs_new:Npn \@@_|_o:ww { \@@_&_o:ww \else: }
+ \cs_new:Npn \@@_|_tuple_o:ww { \@@_&_tuple_o:ww \else: }
+ \cs_new:Npn \@@_tuple_|_o:ww #1; #2; { \@@_exp_after_tuple_o:w #1; }
+ \cs_new:Npn \@@_tuple_|_tuple_o:ww #1; #2;
+ { \@@_exp_after_tuple_o:w #1; }
+\group_end:
+\cs_new:Npn \@@_and_return:wNw #1; \fi: #2;
+ { \fi: \@@_exp_after_o:w #1; }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Ternary operator}
+%
+% \begin{macro}[EXP]
+% {\@@_ternary:NwwN, \@@_ternary_auxi:NwwN, \@@_ternary_auxii:NwwN}
+% The first function receives the test and the true branch of the |?:|
+% ternary operator. It calls \cs{@@_ternary_auxii:NwwN} if the test
+% branch is a floating point number $\pm 0$, and otherwise calls
+% \cs{@@_ternary_auxi:NwwN}. These functions select one of their two
+% arguments.
+% \begin{macrocode}
+\cs_new:Npn \@@_ternary:NwwN #1 #2#3@ #4@ #5
+ {
+ \if_meaning:w \@@_parse_infix_::N #5
+ \if_charcode:w 0
+ \@@_if_type_fp:NTwFw
+ #2 { \use_i:nn \@@_use_i_delimit_by_s_stop:nw #3 \s_@@_stop }
+ \s_@@ 1 \s_@@_stop
+ \exp_after:wN \exp_after:wN \exp_after:wN \@@_ternary_auxii:NwwN
+ \else:
+ \exp_after:wN \exp_after:wN \exp_after:wN \@@_ternary_auxi:NwwN
+ \fi:
+ \exp_after:wN #1
+ \exp:w \exp_end_continue_f:w
+ \@@_exp_after_array_f:w #4 \s_@@_expr_stop
+ \exp_after:wN @
+ \exp:w
+ \@@_parse_operand:Nw \c_@@_prec_colon_int
+ \@@_parse_expand:w
+ \else:
+ \msg_expandable_error:nnnn
+ { fp } { missing } { : } { ~for~?: }
+ \exp_after:wN \@@_parse_continue:NwN
+ \exp_after:wN #1
+ \exp:w \exp_end_continue_f:w
+ \@@_exp_after_array_f:w #4 \s_@@_expr_stop
+ \exp_after:wN #5
+ \exp_after:wN #1
+ \fi:
+ }
+\cs_new:Npn \@@_ternary_auxi:NwwN #1#2@#3@#4
+ {
+ \exp_after:wN \@@_parse_continue:NwN
+ \exp_after:wN #1
+ \exp:w \exp_end_continue_f:w
+ \@@_exp_after_array_f:w #2 \s_@@_expr_stop
+ #4 #1
+ }
+\cs_new:Npn \@@_ternary_auxii:NwwN #1#2@#3@#4
+ {
+ \exp_after:wN \@@_parse_continue:NwN
+ \exp_after:wN #1
+ \exp:w \exp_end_continue_f:w
+ \@@_exp_after_array_f:w #3 \s_@@_expr_stop
+ #4 #1
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macrocode}
+%</package>
+% \end{macrocode}
+%
+% \end{implementation}
+%
+% \PrintChanges
+%
+% \PrintIndex