Here's a fairly general method for any (up to 9) variables.
\documentclass{article}
\usepackage{booktabs}
\ExplSyntaxOn
\NewDocumentCommand{\tableofvalues}{mmmm}
{% #1 = list of variables
% #2 = formula to compute in terms of the variables
% #3 = formula in xfp terms
% #4 = set of comma list separated values for the variables
\seriously_tov_main:nnnn { #1 } { #2 } { #3 } { #4 }
}
% example call
% \tableofvalues{a,b}{a+b}{round(#1+#2,2)}{
% {1, 2.3, 4, -2}
% {1, 1.5, 2, 42}
% }
\tl_new:N \l__seriously_tov_body_tl
\tl_new:N \l__seriously_tov_sgn_tl
\seq_new:N \l__seriously_tov_rows_seq
\seq_new:N \l__seriously_tov_args_seq
\clist_new:c { l__seriously_tov_1_clist }
\clist_new:c { l__seriously_tov_2_clist }
\clist_new:c { l__seriously_tov_3_clist }
\clist_new:c { l__seriously_tov_4_clist }
\clist_new:c { l__seriously_tov_5_clist }
\clist_new:c { l__seriously_tov_6_clist }
\clist_new:c { l__seriously_tov_7_clist }
\clist_new:c { l__seriously_tov_8_clist }
\clist_new:c { l__seriously_tov_9_clist }
\cs_new_protected:Nn \seriously_tov_main:nnnn
{
% compute the number of arguments
\tl_set:Nx \l__seriously_tov_sgn_tl
{
\prg_replicate:nn { \clist_count:n { #1 } } { n }
}
% make a temporary function to do the computation
\cs_set:cn { __seriously_tov_compute:\l__seriously_tov_sgn_tl } { \fp_eval:n { #3 } }
% this is a mouthful, so we define a shorthand
\cs_set_eq:Nc \__seriously_tov_compute:w { __seriously_tov_compute:\l__seriously_tov_sgn_tl }
% we want to absorb the values traversing the lists
\int_step_inline:nn { \clist_count:n { #1 } }
{
\clist_set:cx { l__seriously_tov_##1_clist } { \tl_item:nn { #4 } { ##1 } }
}
\seq_clear:N \l__seriously_tov_rows_seq
\seq_clear:N \l__seriously_tov_args_seq
\int_step_inline:nn { \clist_count:c { l__seriously_tov_1_clist } }
{
\tl_clear:N \l_tmpa_tl \tl_clear:N \l_tmpb_tl
\int_step_inline:nn { \clist_count:n { #1 } }
{
\tl_put_right:Nx \l_tmpa_tl { \clist_item:cn { l__seriously_tov_####1_clist } { ##1 } & }
\tl_put_right:Nx \l_tmpb_tl { { \clist_item:cn { l__seriously_tov_####1_clist } { ##1 } } }
}
\tl_put_left:Nn \l_tmpb_tl { \__seriously_tov_compute:w }
\tl_put_right:Nn \l_tmpb_tl { \\ }
\seq_put_right:NV \l__seriously_tov_rows_seq \l_tmpa_tl
\seq_put_right:NV \l__seriously_tov_args_seq \l_tmpb_tl
}
% now we can build the table body
\tl_set:Nx \l__seriously_tov_body_tl
{
\seq_mapthread_function:NNN \l__seriously_tov_rows_seq \l__seriously_tov_args_seq \use:nn
}
% and we output the table
$\begin{array}{ *{\clist_count:n { #1 }}{c}c }
\toprule
\clist_use:nn { #1 } { & } & #2 \\
\midrule
\tl_use:N \l__seriously_tov_body_tl
\bottomrule
\end{array}$
}
\ExplSyntaxOff
\begin{document}
\tableofvalues{a,b}{a+b}{round(#1+#2,2)}{
{1, 2.3, 4, -2}
{1, 1.5, 2, 42}
}
\bigskip
\tableofvalues{x,y,z}{xyz}{#1*#2*#3}{
{0.3,23,92}
{1,2,3}
{100,200,-100}
}
\end{document}

What's the idea behind this code? A certain number of variables is declared and we can access their number as \clist_count:n{#1}
.
The next task is to define a function that computes the values and which needs to take as many arguments as there are variables. We can therefore exploit expl3
function signatures that will provide the required number of arguments; in the first example of use, we'd get
\cs_set:cn { __seriously_tov_compute:nn } { \fp_eval:n { #3 } }
because we've generated nn
via \prg_replicate:nn
.
However, using this in the following code would be too verbose, so I define an alias with the :w
signature. The replacement text is defined by applying \fp_eval:n
to argument #3
that contains the formula in a syntax suitable for such expressions.
The next task is to generate the list of arguments to be passed to \__seriously_tov_compute:w
. The final argument to \tableofvalues
is expected to contain as many braced lists as there are variables and these lists are supposed to have the same number of items.
These lists are first stored in clist
variables using a loop on the number of variables. Next we traverse these lists simultaneously extracting items with the same position from each and we build two sequences. In the example of use, the first sequence will contain the items (braces only for separating them)
{1 & 1 &}
{2.3 & 1.5 &}
{4 & 2 &}
{-2 & 42 &}
and the second sequence will contain
{\__seriously_tov_compute:w {1}{1} \\}
{\__seriously_tov_compute:w {2.3}{1.5} \\}
{\__seriously_tov_compute:w {4}{2} \\}
{\__seriously_tov_compute:w {-2}{42} \\}
(not really: the sequence will actually contain the computed values).
Finally we build the table body by fetching items from each sequence in order with \seq_mapthread_function:NNN
with the function \use:nn
that simply delivers its arguments one after the other.
Once the table body is ready, the table can be output with a suitable preamble.