[latex3-commits] [git/LaTeX3-latex3-latex3] master: Add the factorial function fact() to l3fp (see #297) (ebc9b69)

Bruno Le Floch bruno at le-floch.fr
Sun Mar 3 20:05:12 CET 2019


Repository : https://github.com/latex3/latex3
On branch  : master
Link       : https://github.com/latex3/latex3/commit/ebc9b69e08d029264f776301e123e391062c6663

>---------------------------------------------------------------

commit ebc9b69e08d029264f776301e123e391062c6663
Author: Bruno Le Floch <bruno at le-floch.fr>
Date:   Sun Mar 3 19:45:37 2019 +0100

    Add the factorial function fact() to l3fp (see #297)
    
    The gamma function is much harder because it is defined at non-integer
    arguments of course.  I've opted for a function fact() to be used
    in expressions because none of the other advanced functions are
    provided as \fp_...:n.  I've hesitated between the full word factorial
    and the shorter name, to be more in line with things like cos or atan.
    This can easily be changed.


>---------------------------------------------------------------

ebc9b69e08d029264f776301e123e391062c6663
 l3kernel/CHANGELOG.md                   |    1 +
 l3kernel/l3fp-aux.dtx                   |    2 +-
 l3kernel/l3fp-expo.dtx                  |  121 +++++++++++++++++++++++++++++++
 l3kernel/l3fp.dtx                       |   13 ++++
 l3kernel/testfiles/m3expl001.luatex.tlg |    7 ++
 l3kernel/testfiles/m3expl001.ptex.tlg   |    7 ++
 l3kernel/testfiles/m3expl001.tlg        |    7 ++
 l3kernel/testfiles/m3expl001.uptex.tlg  |    7 ++
 l3kernel/testfiles/m3expl001.xetex.tlg  |    7 ++
 l3kernel/testfiles/m3expl003.luatex.tlg |    7 ++
 l3kernel/testfiles/m3expl003.ptex.tlg   |    7 ++
 l3kernel/testfiles/m3expl003.tlg        |    7 ++
 l3kernel/testfiles/m3expl003.uptex.tlg  |    7 ++
 l3kernel/testfiles/m3expl003.xetex.tlg  |    7 ++
 l3kernel/testfiles/m3fp-expo001.lvt     |   23 ++++++
 l3kernel/testfiles/m3fp-expo001.tlg     |   67 +++++++++++++++++
 l3packages/xfp/xfp.dtx                  |    1 +
 17 files changed, 297 insertions(+), 1 deletion(-)

diff --git a/l3kernel/CHANGELOG.md b/l3kernel/CHANGELOG.md
index e6a241c..7329c88 100644
--- a/l3kernel/CHANGELOG.md
+++ b/l3kernel/CHANGELOG.md
@@ -12,6 +12,7 @@ this project uses date-based 'snapshot' version identifiers.
 - `\str_log:n`, `\str_log:N`
 - `TF` versions for `\file_get_...:nN` and `\ior_(str_)get:NN` functions
 - `undo-recent-deprecations` option
+- `factorial` function in `l3fp`
 
 ### Changed
 
diff --git a/l3kernel/l3fp-aux.dtx b/l3kernel/l3fp-aux.dtx
index 6784813..a68c99e 100644
--- a/l3kernel/l3fp-aux.dtx
+++ b/l3kernel/l3fp-aux.dtx
@@ -1156,7 +1156,7 @@
 %     \@@_small_int_test:NnnwNTF
 %   }
 %   Tests if the floating point argument is an integer or $\pm\infty$.
-%   If so, it is converted to an integer in the range $[-10^{8},10^{8}]$
+%   If so, it is clipped to an integer in the range $[-10^{8},10^{8}]$
 %   and fed as a braced argument to the \meta{true code}.
 %   Otherwise, the \meta{false code} is performed.
 %
diff --git a/l3kernel/l3fp-expo.dtx b/l3kernel/l3fp-expo.dtx
index 1827c24..41a8d37 100644
--- a/l3kernel/l3fp-expo.dtx
+++ b/l3kernel/l3fp-expo.dtx
@@ -64,6 +64,7 @@
 %   {
 %     \@@_parse_word_exp:N   ,
 %     \@@_parse_word_ln:N    ,
+%     \@@_parse_word_fact:N,
 %   }
 %   Unary functions.
 %    \begin{macrocode}
@@ -71,6 +72,8 @@
   { \@@_parse_unary_function:NNN \@@_exp_o:w ? }
 \cs_new:Npn \@@_parse_word_ln:N
   { \@@_parse_unary_function:NNN \@@_ln_o:w ? }
+\cs_new:Npn \@@_parse_word_fact:N
+  { \@@_parse_unary_function:NNN \@@_fact_o:w ? }
 %    \end{macrocode}
 % \end{macro}
 %
@@ -1247,6 +1250,124 @@
 %    \end{macrocode}
 % \end{macro}
 %
+% \subsection{Factorial}
+%
+% \begin{variable}{\c_@@_fact_max_arg_int}
+%   The maximum integer whose factorial fits in the exponent range is
+%   $3248$, as $3249!\sim 10^{10000.8}$
+%    \begin{macrocode}
+\int_const:Nn \c_@@_fact_max_arg_int { 3248 }
+%    \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}[EXP]{\@@_fact_o:w}
+%   First detect $\pm 0$ and $+\infty$ and \texttt{nan}.  Then note that
+%   factorial of anything with a negative sign (except $-0$) is
+%   undefined.  Then call \cs{@@_small_int:wTF} to get an integer as the
+%   argument, and start a loop.  This is not the most efficient way of
+%   computing the factorial, but it works all right.  Of course we work
+%   with $24$ digits instead of~$16$.  It is easy to check that
+%   computing factorials with this precision is enough.
+%    \begin{macrocode}
+\cs_new:Npn \@@_fact_o:w #1 \s_@@ \@@_chk:w #2#3#4; @
+  {
+    \if_case:w #2 \exp_stop_f:
+      \@@_case_return_o:Nw \c_one_fp
+    \or:
+    \or:
+      \if_meaning:w 0 #3
+        \exp_after:wN \@@_case_return_same_o:w
+      \fi:
+    \or:
+      \@@_case_return_same_o:w
+    \fi:
+    \if_meaning:w 2 #3
+      \@@_case_use:nw { \@@_invalid_operation_o:fw { fact } }
+    \fi:
+    \@@_fact_pos_o:w
+    \s_@@ \@@_chk:w #2 #3 #4 ;
+  }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_fact_pos_o:w, \@@_fact_int_o:w}
+%   Then check the input is an integer, and call
+%   \cs{@@_facorial_int_o:n} with that \texttt{int} as an argument.  If
+%   it's too big the factorial overflows.  Otherwise call
+%   \cs{@@_sanitize:Nw} with a positive sign marker~|0| and an integer
+%   expression that will mop up any exponent in the calculation.
+%    \begin{macrocode}
+\cs_new:Npn \@@_fact_pos_o:w #1;
+  {
+    \@@_small_int:wTF #1;
+      { \@@_fact_int_o:n }
+      { \@@_invalid_operation_o:fw { fact } #1; }
+  }
+\cs_new:Npn \@@_fact_int_o:n #1
+  {
+    \if_int_compare:w #1 > \c_@@_fact_max_arg_int
+      \@@_case_return:nw
+        {
+          \exp_after:wN \exp_after:wN \exp_after:wN \@@_overflow:w
+          \exp_after:wN \c_inf_fp
+        }
+    \fi:
+    \exp_after:wN \@@_sanitize:Nw
+    \exp_after:wN 0
+    \int_value:w \@@_int_eval:w
+    \@@_fact_loop_o:w #1 . 4 , { 1 } { } { } { } { } { } ;
+  }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_fact_loop_o:w}
+%   The loop receives an integer |#1| whose factorial we want to
+%   compute, which we progressively decrement, and the result so far as
+%   an extended-precision number |#2| in the form
+%   \meta{exponent}|,|\meta{mantissa}|;|.  The loop goes in steps of two
+%   because we compute |#1*#1-1| as an integer expression (it must fit
+%   since |#1| is at most $3248$), then multiply with the result so far.
+%   We don't need to fill in most of the mantissa with zeros because
+%   \cs{@@_ep_mul:wwwwn} first normalizes the extended precision number
+%   to avoid loss of precision.  When reaching a small enough number
+%   simply use a table of factorials less than $10^8$.  This limit is
+%   chosen because the normalization step cannot deal with larger
+%   integers.
+%    \begin{macrocode}
+\cs_new:Npn \@@_fact_loop_o:w #1 . #2 ;
+  {
+    \if_int_compare:w #1 < 12 \exp_stop_f:
+      \@@_fact_small_o:w #1
+    \fi:
+    \exp_after:wN \@@_ep_mul:wwwwn
+    \exp_after:wN 4 \exp_after:wN ,
+    \exp_after:wN { \int_value:w \@@_int_eval:w #1 * (#1 - 1) }
+    { } { } { } { } { } ;
+    #2 ;
+    {
+      \exp_after:wN \@@_fact_loop_o:w
+      \int_value:w \@@_int_eval:w #1 - 2 .
+    }
+  }
+\cs_new:Npn \@@_fact_small_o:w #1 \fi: #2 ; #3 ; #4
+  {
+    \fi:
+    \exp_after:wN \@@_ep_mul:wwwwn
+    \exp_after:wN 4 \exp_after:wN ,
+    \exp_after:wN
+      {
+        \int_value:w
+        \if_case:w #1 \exp_stop_f:
+        1 \or: 1 \or: 2 \or: 6 \or: 24 \or: 120 \or: 720 \or: 5040
+        \or: 40320 \or: 362880 \or: 3628800 \or: 39916800
+        \fi:
+      } { } { } { } { } { } ;
+    #3 ;
+    \@@_ep_to_float_o:wwN 0
+  }
+%    \end{macrocode}
+% \end{macro}
+%
 %    \begin{macrocode}
 %</initex|package>
 %    \end{macrocode}
diff --git a/l3kernel/l3fp.dtx b/l3kernel/l3fp.dtx
index 8279700..7ba226b 100644
--- a/l3kernel/l3fp.dtx
+++ b/l3kernel/l3fp.dtx
@@ -73,6 +73,7 @@
 %     $x\mathop{\&\&}y$, disjunction $x\mathop{\vert\vert}y$, ternary
 %     operator $x\mathop{?}y\mathop{:}z$.
 %   \item Exponentials: $\exp x$, $\ln x$, $x^y$.
+%   \item Integer factorial: $\operatorname{fact} x$.
 %   \item Trigonometry: $\sin x$, $\cos x$, $\tan x$, $\cot x$, $\sec
 %     x$, $\csc x$ expecting their arguments in radians, and
 %     $\operatorname{sind} x$, $\operatorname{cosd} x$,
@@ -1028,6 +1029,18 @@
 %   If the operand is a tuple, \enquote{invalid operation} occurs.
 % \end{function}
 %
+% \begin{function}[tested = m3fp-expo001]{fact}
+%   \begin{syntax}
+%     \cs{fp_eval:n} \{ |fact(| \meta{fpexpr} |)| \}
+%   \end{syntax}
+%   Computes the factorial of the \meta{fpexpr}.  If the \meta{fpexpr}
+%   is an integer between $-0$ and $3248$ included, the result is finite
+%   and correctly rounded.  Larger positive integers give $+\infty$ with
+%   \enquote{overflow}, while $|fact(|{+\infty}|)|=+\infty$ and
+%   $|fact(nan)|=|nan|$ with no exception.  All other inputs give \nan{}
+%   with the \enquote{invalid operation} exception.
+% \end{function}
+%
 % \begin{function}[tested = m3fp-expo001]{ln}
 %   \begin{syntax}
 %     \cs{fp_eval:n} \{ |ln(| \meta{fpexpr} |)| \}
diff --git a/l3kernel/testfiles/m3expl001.luatex.tlg b/l3kernel/testfiles/m3expl001.luatex.tlg
index b7cb9c3..ea5de24 100644
--- a/l3kernel/testfiles/m3expl001.luatex.tlg
+++ b/l3kernel/testfiles/m3expl001.luatex.tlg
@@ -4125,6 +4125,7 @@ Defining \__fp_fixed_to_float_pack:ww on line ...
 Defining \__fp_fixed_to_float_round_up:wnnnnw on line ...
 Defining \__fp_parse_word_exp:N on line ...
 Defining \__fp_parse_word_ln:N on line ...
+Defining \__fp_parse_word_fact:N on line ...
 Defining \c__fp_ln_i_fixed_tl on line ...
 Defining \c__fp_ln_ii_fixed_tl on line ...
 Defining \c__fp_ln_iii_fixed_tl on line ...
@@ -4190,6 +4191,12 @@ Defining \__fp_pow_neg_aux:wNN on line ...
 Defining \__fp_pow_neg_case:w on line ...
 Defining \__fp_pow_neg_case_aux:nnnnn on line ...
 Defining \__fp_pow_neg_case_aux:Nnnw on line ...
+Defining \c__fp_fact_max_arg_int on line ...
+Defining \__fp_fact_o:w on line ...
+Defining \__fp_fact_pos_o:w on line ...
+Defining \__fp_fact_int_o:n on line ...
+Defining \__fp_fact_loop_o:w on line ...
+Defining \__fp_fact_small_o:w on line ...
 Defining \__fp_parse_word_acos:N on line ...
 Defining \__fp_parse_word_acosd:N on line ...
 Defining \__fp_parse_word_acsc:N on line ...
diff --git a/l3kernel/testfiles/m3expl001.ptex.tlg b/l3kernel/testfiles/m3expl001.ptex.tlg
index 87c4f8c..7d61902 100644
--- a/l3kernel/testfiles/m3expl001.ptex.tlg
+++ b/l3kernel/testfiles/m3expl001.ptex.tlg
@@ -4422,6 +4422,7 @@ Defining \__fp_fixed_to_float_pack:ww on line ...
 Defining \__fp_fixed_to_float_round_up:wnnnnw on line ...
 Defining \__fp_parse_word_exp:N on line ...
 Defining \__fp_parse_word_ln:N on line ...
+Defining \__fp_parse_word_fact:N on line ...
 Defining \c__fp_ln_i_fixed_tl on line ...
 Defining \c__fp_ln_ii_fixed_tl on line ...
 Defining \c__fp_ln_iii_fixed_tl on line ...
@@ -4487,6 +4488,12 @@ Defining \__fp_pow_neg_aux:wNN on line ...
 Defining \__fp_pow_neg_case:w on line ...
 Defining \__fp_pow_neg_case_aux:nnnnn on line ...
 Defining \__fp_pow_neg_case_aux:Nnnw on line ...
+Defining \c__fp_fact_max_arg_int on line ...
+Defining \__fp_fact_o:w on line ...
+Defining \__fp_fact_pos_o:w on line ...
+Defining \__fp_fact_int_o:n on line ...
+Defining \__fp_fact_loop_o:w on line ...
+Defining \__fp_fact_small_o:w on line ...
 Defining \__fp_parse_word_acos:N on line ...
 Defining \__fp_parse_word_acosd:N on line ...
 Defining \__fp_parse_word_acsc:N on line ...
diff --git a/l3kernel/testfiles/m3expl001.tlg b/l3kernel/testfiles/m3expl001.tlg
index 69d7b69..9d81136 100644
--- a/l3kernel/testfiles/m3expl001.tlg
+++ b/l3kernel/testfiles/m3expl001.tlg
@@ -4422,6 +4422,7 @@ Defining \__fp_fixed_to_float_pack:ww on line ...
 Defining \__fp_fixed_to_float_round_up:wnnnnw on line ...
 Defining \__fp_parse_word_exp:N on line ...
 Defining \__fp_parse_word_ln:N on line ...
+Defining \__fp_parse_word_fact:N on line ...
 Defining \c__fp_ln_i_fixed_tl on line ...
 Defining \c__fp_ln_ii_fixed_tl on line ...
 Defining \c__fp_ln_iii_fixed_tl on line ...
@@ -4487,6 +4488,12 @@ Defining \__fp_pow_neg_aux:wNN on line ...
 Defining \__fp_pow_neg_case:w on line ...
 Defining \__fp_pow_neg_case_aux:nnnnn on line ...
 Defining \__fp_pow_neg_case_aux:Nnnw on line ...
+Defining \c__fp_fact_max_arg_int on line ...
+Defining \__fp_fact_o:w on line ...
+Defining \__fp_fact_pos_o:w on line ...
+Defining \__fp_fact_int_o:n on line ...
+Defining \__fp_fact_loop_o:w on line ...
+Defining \__fp_fact_small_o:w on line ...
 Defining \__fp_parse_word_acos:N on line ...
 Defining \__fp_parse_word_acosd:N on line ...
 Defining \__fp_parse_word_acsc:N on line ...
diff --git a/l3kernel/testfiles/m3expl001.uptex.tlg b/l3kernel/testfiles/m3expl001.uptex.tlg
index 786cd38..0d0671e 100644
--- a/l3kernel/testfiles/m3expl001.uptex.tlg
+++ b/l3kernel/testfiles/m3expl001.uptex.tlg
@@ -4422,6 +4422,7 @@ Defining \__fp_fixed_to_float_pack:ww on line ...
 Defining \__fp_fixed_to_float_round_up:wnnnnw on line ...
 Defining \__fp_parse_word_exp:N on line ...
 Defining \__fp_parse_word_ln:N on line ...
+Defining \__fp_parse_word_fact:N on line ...
 Defining \c__fp_ln_i_fixed_tl on line ...
 Defining \c__fp_ln_ii_fixed_tl on line ...
 Defining \c__fp_ln_iii_fixed_tl on line ...
@@ -4487,6 +4488,12 @@ Defining \__fp_pow_neg_aux:wNN on line ...
 Defining \__fp_pow_neg_case:w on line ...
 Defining \__fp_pow_neg_case_aux:nnnnn on line ...
 Defining \__fp_pow_neg_case_aux:Nnnw on line ...
+Defining \c__fp_fact_max_arg_int on line ...
+Defining \__fp_fact_o:w on line ...
+Defining \__fp_fact_pos_o:w on line ...
+Defining \__fp_fact_int_o:n on line ...
+Defining \__fp_fact_loop_o:w on line ...
+Defining \__fp_fact_small_o:w on line ...
 Defining \__fp_parse_word_acos:N on line ...
 Defining \__fp_parse_word_acosd:N on line ...
 Defining \__fp_parse_word_acsc:N on line ...
diff --git a/l3kernel/testfiles/m3expl001.xetex.tlg b/l3kernel/testfiles/m3expl001.xetex.tlg
index 2dacf2b..03db412 100644
--- a/l3kernel/testfiles/m3expl001.xetex.tlg
+++ b/l3kernel/testfiles/m3expl001.xetex.tlg
@@ -4160,6 +4160,7 @@ Defining \__fp_fixed_to_float_pack:ww on line ...
 Defining \__fp_fixed_to_float_round_up:wnnnnw on line ...
 Defining \__fp_parse_word_exp:N on line ...
 Defining \__fp_parse_word_ln:N on line ...
+Defining \__fp_parse_word_fact:N on line ...
 Defining \c__fp_ln_i_fixed_tl on line ...
 Defining \c__fp_ln_ii_fixed_tl on line ...
 Defining \c__fp_ln_iii_fixed_tl on line ...
@@ -4225,6 +4226,12 @@ Defining \__fp_pow_neg_aux:wNN on line ...
 Defining \__fp_pow_neg_case:w on line ...
 Defining \__fp_pow_neg_case_aux:nnnnn on line ...
 Defining \__fp_pow_neg_case_aux:Nnnw on line ...
+Defining \c__fp_fact_max_arg_int on line ...
+Defining \__fp_fact_o:w on line ...
+Defining \__fp_fact_pos_o:w on line ...
+Defining \__fp_fact_int_o:n on line ...
+Defining \__fp_fact_loop_o:w on line ...
+Defining \__fp_fact_small_o:w on line ...
 Defining \__fp_parse_word_acos:N on line ...
 Defining \__fp_parse_word_acosd:N on line ...
 Defining \__fp_parse_word_acsc:N on line ...
diff --git a/l3kernel/testfiles/m3expl003.luatex.tlg b/l3kernel/testfiles/m3expl003.luatex.tlg
index b7cb9c3..ea5de24 100644
--- a/l3kernel/testfiles/m3expl003.luatex.tlg
+++ b/l3kernel/testfiles/m3expl003.luatex.tlg
@@ -4125,6 +4125,7 @@ Defining \__fp_fixed_to_float_pack:ww on line ...
 Defining \__fp_fixed_to_float_round_up:wnnnnw on line ...
 Defining \__fp_parse_word_exp:N on line ...
 Defining \__fp_parse_word_ln:N on line ...
+Defining \__fp_parse_word_fact:N on line ...
 Defining \c__fp_ln_i_fixed_tl on line ...
 Defining \c__fp_ln_ii_fixed_tl on line ...
 Defining \c__fp_ln_iii_fixed_tl on line ...
@@ -4190,6 +4191,12 @@ Defining \__fp_pow_neg_aux:wNN on line ...
 Defining \__fp_pow_neg_case:w on line ...
 Defining \__fp_pow_neg_case_aux:nnnnn on line ...
 Defining \__fp_pow_neg_case_aux:Nnnw on line ...
+Defining \c__fp_fact_max_arg_int on line ...
+Defining \__fp_fact_o:w on line ...
+Defining \__fp_fact_pos_o:w on line ...
+Defining \__fp_fact_int_o:n on line ...
+Defining \__fp_fact_loop_o:w on line ...
+Defining \__fp_fact_small_o:w on line ...
 Defining \__fp_parse_word_acos:N on line ...
 Defining \__fp_parse_word_acosd:N on line ...
 Defining \__fp_parse_word_acsc:N on line ...
diff --git a/l3kernel/testfiles/m3expl003.ptex.tlg b/l3kernel/testfiles/m3expl003.ptex.tlg
index 87c4f8c..7d61902 100644
--- a/l3kernel/testfiles/m3expl003.ptex.tlg
+++ b/l3kernel/testfiles/m3expl003.ptex.tlg
@@ -4422,6 +4422,7 @@ Defining \__fp_fixed_to_float_pack:ww on line ...
 Defining \__fp_fixed_to_float_round_up:wnnnnw on line ...
 Defining \__fp_parse_word_exp:N on line ...
 Defining \__fp_parse_word_ln:N on line ...
+Defining \__fp_parse_word_fact:N on line ...
 Defining \c__fp_ln_i_fixed_tl on line ...
 Defining \c__fp_ln_ii_fixed_tl on line ...
 Defining \c__fp_ln_iii_fixed_tl on line ...
@@ -4487,6 +4488,12 @@ Defining \__fp_pow_neg_aux:wNN on line ...
 Defining \__fp_pow_neg_case:w on line ...
 Defining \__fp_pow_neg_case_aux:nnnnn on line ...
 Defining \__fp_pow_neg_case_aux:Nnnw on line ...
+Defining \c__fp_fact_max_arg_int on line ...
+Defining \__fp_fact_o:w on line ...
+Defining \__fp_fact_pos_o:w on line ...
+Defining \__fp_fact_int_o:n on line ...
+Defining \__fp_fact_loop_o:w on line ...
+Defining \__fp_fact_small_o:w on line ...
 Defining \__fp_parse_word_acos:N on line ...
 Defining \__fp_parse_word_acosd:N on line ...
 Defining \__fp_parse_word_acsc:N on line ...
diff --git a/l3kernel/testfiles/m3expl003.tlg b/l3kernel/testfiles/m3expl003.tlg
index 69d7b69..9d81136 100644
--- a/l3kernel/testfiles/m3expl003.tlg
+++ b/l3kernel/testfiles/m3expl003.tlg
@@ -4422,6 +4422,7 @@ Defining \__fp_fixed_to_float_pack:ww on line ...
 Defining \__fp_fixed_to_float_round_up:wnnnnw on line ...
 Defining \__fp_parse_word_exp:N on line ...
 Defining \__fp_parse_word_ln:N on line ...
+Defining \__fp_parse_word_fact:N on line ...
 Defining \c__fp_ln_i_fixed_tl on line ...
 Defining \c__fp_ln_ii_fixed_tl on line ...
 Defining \c__fp_ln_iii_fixed_tl on line ...
@@ -4487,6 +4488,12 @@ Defining \__fp_pow_neg_aux:wNN on line ...
 Defining \__fp_pow_neg_case:w on line ...
 Defining \__fp_pow_neg_case_aux:nnnnn on line ...
 Defining \__fp_pow_neg_case_aux:Nnnw on line ...
+Defining \c__fp_fact_max_arg_int on line ...
+Defining \__fp_fact_o:w on line ...
+Defining \__fp_fact_pos_o:w on line ...
+Defining \__fp_fact_int_o:n on line ...
+Defining \__fp_fact_loop_o:w on line ...
+Defining \__fp_fact_small_o:w on line ...
 Defining \__fp_parse_word_acos:N on line ...
 Defining \__fp_parse_word_acosd:N on line ...
 Defining \__fp_parse_word_acsc:N on line ...
diff --git a/l3kernel/testfiles/m3expl003.uptex.tlg b/l3kernel/testfiles/m3expl003.uptex.tlg
index 786cd38..0d0671e 100644
--- a/l3kernel/testfiles/m3expl003.uptex.tlg
+++ b/l3kernel/testfiles/m3expl003.uptex.tlg
@@ -4422,6 +4422,7 @@ Defining \__fp_fixed_to_float_pack:ww on line ...
 Defining \__fp_fixed_to_float_round_up:wnnnnw on line ...
 Defining \__fp_parse_word_exp:N on line ...
 Defining \__fp_parse_word_ln:N on line ...
+Defining \__fp_parse_word_fact:N on line ...
 Defining \c__fp_ln_i_fixed_tl on line ...
 Defining \c__fp_ln_ii_fixed_tl on line ...
 Defining \c__fp_ln_iii_fixed_tl on line ...
@@ -4487,6 +4488,12 @@ Defining \__fp_pow_neg_aux:wNN on line ...
 Defining \__fp_pow_neg_case:w on line ...
 Defining \__fp_pow_neg_case_aux:nnnnn on line ...
 Defining \__fp_pow_neg_case_aux:Nnnw on line ...
+Defining \c__fp_fact_max_arg_int on line ...
+Defining \__fp_fact_o:w on line ...
+Defining \__fp_fact_pos_o:w on line ...
+Defining \__fp_fact_int_o:n on line ...
+Defining \__fp_fact_loop_o:w on line ...
+Defining \__fp_fact_small_o:w on line ...
 Defining \__fp_parse_word_acos:N on line ...
 Defining \__fp_parse_word_acosd:N on line ...
 Defining \__fp_parse_word_acsc:N on line ...
diff --git a/l3kernel/testfiles/m3expl003.xetex.tlg b/l3kernel/testfiles/m3expl003.xetex.tlg
index 2dacf2b..03db412 100644
--- a/l3kernel/testfiles/m3expl003.xetex.tlg
+++ b/l3kernel/testfiles/m3expl003.xetex.tlg
@@ -4160,6 +4160,7 @@ Defining \__fp_fixed_to_float_pack:ww on line ...
 Defining \__fp_fixed_to_float_round_up:wnnnnw on line ...
 Defining \__fp_parse_word_exp:N on line ...
 Defining \__fp_parse_word_ln:N on line ...
+Defining \__fp_parse_word_fact:N on line ...
 Defining \c__fp_ln_i_fixed_tl on line ...
 Defining \c__fp_ln_ii_fixed_tl on line ...
 Defining \c__fp_ln_iii_fixed_tl on line ...
@@ -4225,6 +4226,12 @@ Defining \__fp_pow_neg_aux:wNN on line ...
 Defining \__fp_pow_neg_case:w on line ...
 Defining \__fp_pow_neg_case_aux:nnnnn on line ...
 Defining \__fp_pow_neg_case_aux:Nnnw on line ...
+Defining \c__fp_fact_max_arg_int on line ...
+Defining \__fp_fact_o:w on line ...
+Defining \__fp_fact_pos_o:w on line ...
+Defining \__fp_fact_int_o:n on line ...
+Defining \__fp_fact_loop_o:w on line ...
+Defining \__fp_fact_small_o:w on line ...
 Defining \__fp_parse_word_acos:N on line ...
 Defining \__fp_parse_word_acosd:N on line ...
 Defining \__fp_parse_word_acsc:N on line ...
diff --git a/l3kernel/testfiles/m3fp-expo001.lvt b/l3kernel/testfiles/m3fp-expo001.lvt
index 8fb8a6a..f8e32db 100644
--- a/l3kernel/testfiles/m3fp-expo001.lvt
+++ b/l3kernel/testfiles/m3fp-expo001.lvt
@@ -185,4 +185,27 @@
   }
 
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\TESTEXP { Factorial }
+  {
+    \fp_to_tl:n { fact(nan) } \NEWLINE
+    \fp_to_tl:n { fact(inf) } \NEWLINE
+    \fp_to_tl:n { fact(-inf) } \NEWLINE
+    \fp_to_tl:n { fact(1234567.891234567) } \NEWLINE
+    \fp_to_tl:n { fact(1e-9999) } \NEWLINE
+    \fp_to_tl:n { fact(-1.2) } \NEWLINE
+    \fp_to_tl:n { fact(-1) } \NEWLINE
+    \fp_to_tl:n { fact(-0) } \NEWLINE
+    \fp_to_tl:n { fact(0) } \NEWLINE
+    \fp_to_tl:n { fact(1) } \NEWLINE
+    \fp_to_tl:n { fact(2) } \NEWLINE
+    \fp_to_tl:n { fact(10) } \NEWLINE
+    \fp_to_tl:n { fact(21) } \NEWLINE
+    \fp_to_tl:n { fact(318) } \NEWLINE
+    \fp_to_tl:n { fact(3248) } \NEWLINE
+    \fp_to_tl:n { fact(3249) } \NEWLINE
+    \fp_to_tl:n { fact(123456789) } \NEWLINE
+    \fp_to_tl:n { fact(123456789e10) } \NEWLINE
+  }
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 \END
diff --git a/l3kernel/testfiles/m3fp-expo001.tlg b/l3kernel/testfiles/m3fp-expo001.tlg
index 7d00664..240c632 100644
--- a/l3kernel/testfiles/m3fp-expo001.tlg
+++ b/l3kernel/testfiles/m3fp-expo001.tlg
@@ -506,3 +506,70 @@ TEST 7: Logarithm
 9.896001841771208e-4
 4.999649966694907e-4
 ============================================================
+============================================================
+TEST 8: Factorial
+============================================================
+! Undefined control sequence.
+<argument> \LaTeX3 error: 
+                           Invalid operation fact(-inf)
+l. ...  }
+The control sequence at the end of the top line
+of your error message was never \def'ed. If you have
+misspelled it (e.g., `\hobx'), type `I' and the correct
+spelling (e.g., `I\hbox'). Otherwise just continue,
+and I'll forget about whatever was undefined.
+! Undefined control sequence.
+<argument> \LaTeX3 error: 
+                           Invalid operation fact(1234567.891234567)
+l. ...  }
+The control sequence at the end of the top line
+of your error message was never \def'ed. If you have
+misspelled it (e.g., `\hobx'), type `I' and the correct
+spelling (e.g., `I\hbox'). Otherwise just continue,
+and I'll forget about whatever was undefined.
+! Undefined control sequence.
+<argument> \LaTeX3 error: 
+                           Invalid operation fact(1e-9999)
+l. ...  }
+The control sequence at the end of the top line
+of your error message was never \def'ed. If you have
+misspelled it (e.g., `\hobx'), type `I' and the correct
+spelling (e.g., `I\hbox'). Otherwise just continue,
+and I'll forget about whatever was undefined.
+! Undefined control sequence.
+<argument> \LaTeX3 error: 
+                           Invalid operation fact(-1.2)
+l. ...  }
+The control sequence at the end of the top line
+of your error message was never \def'ed. If you have
+misspelled it (e.g., `\hobx'), type `I' and the correct
+spelling (e.g., `I\hbox'). Otherwise just continue,
+and I'll forget about whatever was undefined.
+! Undefined control sequence.
+<argument> \LaTeX3 error: 
+                           Invalid operation fact(-1)
+l. ...  }
+The control sequence at the end of the top line
+of your error message was never \def'ed. If you have
+misspelled it (e.g., `\hobx'), type `I' and the correct
+spelling (e.g., `I\hbox'). Otherwise just continue,
+and I'll forget about whatever was undefined.
+nan
+inf
+nan
+nan
+nan
+nan
+nan
+1
+1
+1
+2
+3628800
+5.109094217170944e19
+2.072985253937355e659
+1.973634253086043e9997
+inf
+inf
+inf
+============================================================
diff --git a/l3packages/xfp/xfp.dtx b/l3packages/xfp/xfp.dtx
index a1fe239..9b33674 100644
--- a/l3packages/xfp/xfp.dtx
+++ b/l3packages/xfp/xfp.dtx
@@ -95,6 +95,7 @@
 %     $x\mathop{\&\&}y$, disjunction $x\mathop{\vert\vert}y$, ternary
 %     operator $x\mathop{?}y\mathop{:}z$.
 %   \item Exponentials: $\exp x$, $\ln x$, $x^y$.
+%   \item Integer factorial: $\operatorname{fact} x$.
 %   \item Trigonometry: $\sin x$, $\cos x$, $\tan x$, $\cot x$, $\sec
 %     x$, $\csc x$ expecting their arguments in radians, and
 %     $\operatorname{sind} x$, $\operatorname{cosd} x$,





More information about the latex3-commits mailing list