# Creating an automated table of signs

When solving some inequalities in the context of algebra-precalculus course it is sometimes useful to sketch the behavior of the factors involved using a table. All becomes clear with an example - say we want to solve: the associated table is: If in the inequality is a factor of the form 1/(x-a) it's sometimes indicated in the table by using a double line || under the a. Let's call these restrictions.

When teaching calculus there are some similar tables to deal with the sign of the derivatives, concavity, critical points, and so on.

What I want is:

• a new type of table environment which takes as arguments the numbers where the factors change sign (-3, 0, 1).
• a way to indicate that such a number is a restriction.
• the first two lines in the example must be typed like:

\begin{signtable}{-3}{0}{1}
x & -  & - & + & + \\
\end{signtable}


So I post this because I think there are many possible ways to solve this and I want to know them.

I wonder if this can be done better with TikZ.

So far, the code of the example is:

\documentclass{article}
\usepackage{array}

\begin{document}
\begin{array}{*{5}{c|}@{}c@{}}
\multicolumn{6}{@{}c@{}}{
\begin{array}{*{10}{@{}l@{}}}
\phantom{x(x-1)(x+3} & -\infty & \hspace*{5pt} & -3 & \phantom{-} & 0 & \phantom{+}\hspace*{3pt} & 1 & \phantom{+} & +\infty
\end{array}
}\\
\cline{1-5}
x                     &  - & - & + & + &\\
\cline{1-5}
x-1                   &  - & - & - & + &\\
\cline{1-5}
x+3                   &  - & + & + & + &\\
\cline{1-5}
x(x-1)(x+3)           &  - & + & - & + &
\end{array}
\end{document}


Before you invest too much time in creating a new package I recommend that you look at the packages tableaux, tablor and tkz-tab. The examples of code is readable, however all the documentation for all three packages are in French. I have successfully run some of the examples in these manuals as demonstrations for my LateX class students. Use http://www.texdoc.net/ to get the documentation for each of these quickly.

I thought this was a good idea and might be fun, so I had a go at it. I intend on using this myself, so it strays from the specifics of the question but keeping the question in mind I got it pretty close.

• Syntax: \SignTable{comma separated factors}{comma separated zeros}
• The variable must be z
• accepts any functions recognized by l3fp. I used a few below, more can be found in the documentation. Syntax is important, use proper bracketing and multiplication symbols when appropriate. Exception: functions like sin z, ln z and exp z.
• This can only be trusted if the values used are zero's/discontinuities of the function and that all are accounted for on whatever interval you're interested in. If you just pick a bunch of random numbers, errors may be introduced. I'm mostly sure it can be trusted anyway, I spent a lot of time ironing things out.
• Be careful of using a function like ln x and testing negative values
• To get the critical numbers centred over columns I did something hackish (I'm sure it can be argued that my entire wall of code is hackish!), this made it difficult to use vertical rules and have them look ok (aside from the booktabs rules/vertical rules interaction. Thus, no vertical rules.
• To mark "restrictions" I used an \fbox other things could be tried.

\SignTable{sin z,z+6,2^z,z^3,1/(z+4)}{-6,-4,0,\pi} produces the following (and yes I ignored some zero's of sin z:) Wall of Code

\documentclass{article}
\usepackage{xparse}
\usepackage{booktabs}
\usepackage{array}
\usepackage{mathtools}
\ExplSyntaxOn

% table rows
\tl_new:N \l_ft_rows_tl
% actual function
\tl_new:N \g_the_funct_tl
% table setup, first and last rows require special handling
\tl_new:N \g_ft_first_row_tl
\tl_new:N \l_ft_col_set_tl
\tl_new:N \l_last_row_tl
% holds points of discontinuity, if any
\seq_new:N \g_asys_seq
% set of (loosely) "critical points"
\seq_new:N \g_ft_pts_seq
% pts at which to evaluate function to determine sign
\seq_new:N \g_ft_eval_pts_seq
% holds the factors that are input
\seq_new:N \g_ft_functs_seq

% arg 1 is comma separated list of factors
% arg2 is set of "critical points"
\NewDocumentCommand { \SignTable } { m m }
{
% function names should be fairly self explanatory
\ft_def_functs:n {#1}
\ft_set_vals:n {#2}
\seq_map_function:NN \g_ft_functs_seq \ft_eval_funct_rows:n
% this looks for entered values that give infinite results and marks them.
\seq_map_function:NN \g_ft_functs_seq \ft_find_asys:n
\ft_last_row:
\ft_set_cols:
\ft_first_row:
% space the table a bit more openly
\renewcommand{\arraystretch}{1.5}
% \l_ft_col_set_tl contains column spec
\exp_args:Nnx\begin{array}{\tl_use:N \l_ft_col_set_tl}
\tl_use:N \g_ft_first_row_tl
\toprule
\tl_use:N \l_ft_rows_tl
\bottomrule
\tl_use:N \l_last_row_tl
\end{array}
}

% grab factors from arg
\cs_new:Npn \ft_def_functs:n #1
{
\seq_gset_split:Nnn \g_ft_functs_seq { , } {#1}
}

% just average consecutive critical numbers for test points
\cs_new:Npn \ft_set_vals:n #1
{
\seq_gset_split:Nnn \g_ft_pts_seq { , } {#1}
\seq_set_eq:NN \l_tmpa_seq \g_ft_pts_seq
\group_begin:
\seq_get_left:NN \g_ft_pts_seq \l_tmpa_fp
\seq_put_left:Nx \g_ft_pts_seq {\fp_eval:n {\l_tmpa_fp-1}}
\seq_get_right:NN \l_tmpa_seq \l_tmpa_fp
\seq_put_right:Nx \l_tmpa_seq {\fp_eval:n {\l_tmpa_fp+1}}
\group_end:
}
% helper function for the above
\cs_new:Npn \ft_avg:nn #1#2
{
\seq_gput_right:Nx \g_ft_eval_pts_seq {\fp_eval:n {#1/2+#2/2}}
}

% receives a factor, does some formatting, plugs in some numbers, test sign,
% print appropriate sign.
\cs_new:Npn \ft_eval_funct_rows:n #1
{
\tl_set:Nn \l_tmpa_tl {#1}
\tl_gput_right:Nx \g_the_funct_tl {(\tl_use:N \l_tmpa_tl)*}
\tl_remove_all:Nn \l_tmpa_tl {*}
\tl_put_right:NV \l_ft_rows_tl \l_tmpa_tl
\seq_map_inline:Nn \g_ft_eval_pts_seq
{
\tl_set:Nn \l_tmpa_tl {#1}
\tl_replace_all:Nnn \l_tmpa_tl {z} {(##1)}
\fp_compare:nNnTF {\fp_eval:n {\l_tmpa_tl}}< {0}
{\tl_put_right:Nn \l_ft_rows_tl {&&-}}
{\tl_put_right:Nn \l_ft_rows_tl {&&+}}
}
\tl_put_right:Nn \l_ft_rows_tl {&\\}
}

% receives a factor, plugs in critical points to see if any give infinite result
% if yes, add point to asys sequence.
\cs_new:Npn \ft_find_asys:n #1
{
\group_begin:
\fp_trap:nn {invalid_operation}{none}
\seq_map_inline:Nn \g_ft_pts_seq
{
\tl_set:Nn \l_tmpa_tl {#1}
\tl_replace_all:Nnn \l_tmpa_tl {z} {(##1)}
\fp_compare:nNnT {\fp_eval:n {\l_tmpa_tl}}={inf}
{\seq_gput_right:Nn \g_asys_seq {##1}}
}
\group_end:
}

\cs_new:Npn \ft_last_row:
{
% remove an extra mult operator, this is ugly
\tl_put_right:Nn \l_last_row_tl {f(z)}
\tl_reverse:N \g_the_funct_tl
\tl_set:Nx \g_the_funct_tl {\tl_tail:N \g_the_funct_tl}
\tl_reverse:N \g_the_funct_tl
% if you want the function to appear rather than f(z)
% uncomment the below and comment the first line above.
%\group_begin:
%\tl_remove_all:Nn \g_the_funct_tl {*}
%\tl_gput_right:NV \l_last_row_tl \g_the_funct_tl
%\group_end:
\seq_map_inline:Nn \g_ft_eval_pts_seq
{
\tl_set_eq:NN \l_tmpa_tl \g_the_funct_tl
\tl_replace_all:Nnn \l_tmpa_tl {z} {(##1)}
\fp_compare:nNnTF {\fp_eval:n {\l_tmpa_tl}}< {0}
{\tl_put_right:Nn \l_last_row_tl {&&-}}
{\tl_put_right:Nn \l_last_row_tl {&&+}}
}
\tl_put_right:Nn \l_last_row_tl {&}
}

% alternating centered and centered with zero width.
\cs_new:Npn \ft_set_cols:
{
\prg_replicate:nn{2+\seq_count:N \g_ft_pts_seq}{\tl_put_right:Nn \l_ft_col_set_tl {cm{0pt}}}
}

% to center the critical points on the column separators, all I could think of was to make the
% headings have zero size and center them on columns of zero width
\cs_new:Npn \ft_first_row:
{
\tl_put_right:Nn \g_ft_first_row_tl {&$\mathclap{\underset{\phantom{\downarrow}}{-\infty}}$&}
\seq_map_inline:Nn \g_ft_pts_seq
{
\tl_put_right:Nn \g_ft_first_row_tl {&$\mathclap{\underset{\downarrow}{\is_asy:n {##1}}}$&}
}
\tl_put_right:Nn \g_ft_first_row_tl {&$\mathclap{\underset{\phantom{\downarrow}}{\infty}}$\\}
}

% do something to the points of discontinuity
% change this to whatever works
\cs_new:Npn \is_asy:n #1
{
\seq_if_in:NnTF \g_asys_seq {#1}
{\fboxsep=1pt \fbox{$#1$}}
{#1}
}

\ExplSyntaxOff
\begin{document}

$\SignTable{sin z,z+6,2^z, z^3,1/(z+4)}{-6,-4,0,\pi}$

\end{document}

• @BrunoLeFloch Here's where I wanted \fp_trap:nn. I'm not sure if this was the intended use or even whether it's appropriate here, but I found it quite handy! – Scott H. Aug 28 '12 at 7:47
• a silly niggle ... the baseline of the boxed $-4$` is a hair above the rest of the header labels, putting the minus signs off a nice even line. (in something that looks this nice, tiny anomalies become more obvious.) – barbara beeton Jul 29 '15 at 18:09