为什么 pgfmaths 的 ifthenelse 要评估两个表达式?

为什么 pgfmaths 的 ifthenelse 要评估两个表达式?

考虑必须使用可能未针对某些值定义的运算来计算某些值,例如正切函数。为了避免引发异常,我尝试使用数学's ifthenelse(<expression>, <true code>, <false code>),如下例所示:

\documentclass[tikz, border=2mm]{standalone}
\usepackage{xifthen}

\begin{document}

\begin{tikzpicture}
    \foreach \D [count=\C] in {85,...,95}
    {   \pgfmathsetmacro{\goodAngle}{ifthenelse(or(\D<=86, \D>=94), 1, 0)}
        \pgfmathsetmacro{\Tan}{ifthenelse(\goodAngle == 1, tan(\D), 0)}
        %\ifthenelse{\goodAngle = 1}
        %   {   \pgfmathsetmacro{\Tan}{tan(\D)}}
        %   {   \pgfmathsetmacro{\Tan}{0}}
        \node[right] at (0,-\C*0.7) {\D$^{\circ}$, \goodAngle, \Tan};
    }
\end{tikzpicture}

\end{document}

我原本以为,如果之前确定给定角度是一个好角度,它只会计算该角度的正切(排除 90° 就足够了,但这里我排除了 87° 到 93° 的角度),但它似乎在计算两个表达式,显然 90° 不成立。如果“一步到位”地执行,即不计算第一步\goodAngle,以及使用 的简写符号ifthenelse(即<expression> ? <true code> : <false code>),结果是一样的。为了检查我的想法是否有缺陷,我使用了\ifthenelse西弗森包,正如预期的那样,它只评估正确的表达式。如果取消注释三行注释,并注释掉上面的行,如下所示

\begin{tikzpicture}
    \foreach \D [count=\C] in {85,...,95}
    {   \pgfmathsetmacro{\goodAngle}{ifthenelse(or(\D<=86, \D>=94), 1, 0)}
        %\pgfmathsetmacro{\Tan}{ifthenelse(\goodAngle == 1, tan(\D), 0)}
        \ifthenelse{\goodAngle = 1}
            {   \pgfmathsetmacro{\Tan}{tan(\D)}}
            {   \pgfmathsetmacro{\Tan}{0}}
        \node[right] at (0,-\C*0.7) {\D$^{\circ}$, \goodAngle, \Tan};
    }
\end{tikzpicture}

获得预期结果:

在此处输入图片描述

这种行为并不局限于正切函数,它也出现在简单的除以零的情况下,就像在

\begin{tikzpicture}
    \foreach \D [count=\C] in {-5,...,5}
    {   \pgfmathsetmacro{\goodValue}{ifthenelse(or(\D<=-2, \D>=2), 1, 0)}
        \pgfmathsetmacro{\byZero}{ifthenelse(\goodValue == 1, 1/\D, -1)}
        %\ifthenelse{\goodValue = 1}
        %   {   \pgfmathsetmacro{\byZero}{1/\D}}
        %   {   \pgfmathsetmacro{\byZero}{-1}}
        \node[right] at (0,-\C*0.7) {\D, \goodValue, \byZero};
    }
\end{tikzpicture}

这是预期的行为吗(我对此表示怀疑),还是这是一个错误?是否有设施数学像一个try ... except声明,或者更好的是,一份非神奇宝贝异常方法可以让我捕获特定错误类型,例如try ... except ErrorType1 ... except ErrorTypeN ... finally构造?如果没有,那么在 TeX 世界中是否存在类似的东西,甚至有可能吗?

答案1

至少目前看来,答案似乎是“确实如此。”

幸运的是,人们可以找到解决这个问题的方法。pgf图能够处理无穷大或未定义表达式等值。例如,可以定义自定义函数,如“sinc”或“bounded tan”,它们可以很好地与pgfmath 的 ifthenelse

\pgfmathdeclarefunction{sincf}{1}%
{\pgfmathparse{(abs(#1)<0.01) ? 1 : sin(pi*#1 r)/(pi*#1)}%
}

\pgfmathdeclarefunction{tanny}{1}%
{\pgfmathparse{abs(tan(#1 r))> 1.345 ? 0 : tan(#1 r)}%
}

\begin{tikzpicture}
    \begin{axis}
        \addplot[domain=-2*pi:2*pi, samples=100, red] {sincf(x)};
        \addplot[domain=-2*pi:2*pi, samples=1000, blue] {tanny(x)};
    \end{axis}  
\end{tikzpicture}

在此处输入图片描述

但即使不手动处理“有问题”的值,普格夫可以通过在这些点处中断绘图或将图形连接到下一个有效点来处理这些值:

\begin{tikzpicture}
    \begin{axis}
    [   restrict y to domain=-1.345:1.345,
    ]
        \addplot[domain=-2*pi:2*pi, samples=1000, blue, unbounded coords=jump] {tan(x/pi*180)};
        \addplot[domain=-2*pi:2*pi, samples=1000, red, unbounded coords=discard] {tan(x/pi*180-90)};
    \end{axis}  
\end{tikzpicture}

在此处输入图片描述


但是,如果要计算一些长度以用作绘图中的距离,事情就会变得复杂。下面需要 19 条垂直线,中间那条线的长度为零。(警告:以下代码失败!请勿使用!

\begin{tikzpicture}
    \foreach \x in {0,10,...,180}
    { \pgfmathsetmacro{\Length}{\x==90 ? 0 : tan(\x)}
        \draw (\x/18,0) -- ++ (0,\Length);
    }
\end{tikzpicture}

由于数学评估这两种选择都会产生错误(零除错误,因为 tan(x)=sin(x)/cos(x) 和 cos(90°) = 0)。在这种情况下,必须使用另一种方法排除有问题的点。

第一种替代方法是使用\ifnum<n1><relation><n2> <true expression> \else <false expression \fi>。然后可以按如下方式修复前面的用例:

\begin{tikzpicture}
    \foreach \x in {0,10,...,180}
    { \pgfmathsetmacro{\Length}{\ifnum\x=90 0 \else tan(\x)\fi}
        \draw (\x/18,0) -- ++ (0,\Length);
    }
\end{tikzpicture}

也可以使用外部包,例如西弗森,它提供了一个函数\ifthenelse{<test>}{<true expression>}{false expression}。它不能在pgfmath 的宏赋值,而是必须在两个表达式中都写宏赋值:

\begin{tikzpicture}
    \foreach \x in {0,10,...,180}
    { \ifthenelse{\x=90}
            {   \pgfmathsetmacro{\Length}{0}}
            {   \pgfmathsetmacro{\Length}{tan(\x)}}
        \draw (\x/18,0) -- ++ (0,\Length);
    }
\end{tikzpicture}

这两种方法都有一个很大的缺点:它们只适用于整数检查。如果使用非整数值,可以使用组合方法。数学能够处理浮点测试,它用于确定参数是否有问题,并将变量设置为整数值。然后使用上述替代方案之一。

如果在上面的例子中,角度被转换为辐射,则可以例如执行以下操作:

\begin{tikzpicture}
    \foreach \x in {0,10,...,180}
    { \pgfmathsetmacro{\RadAngle}{\x/180*pi}
        \pgfmathsetmacro{\GoodAngle}{abs(\RadAngle-pi/2) > 0.01 ? 1 : 0}
        \pgfmathsetmacro{\Length}{\ifnum\GoodAngle=1 tan(\RadAngle*180/pi)\else 0\fi}
        \draw (\x/18,0) -- ++ (0,\Length);
    }
\end{tikzpicture}

所有这三种解决方法都会产生相同的输出:

在此处输入图片描述


所有示例的完整代码:

\documentclass[tikz, border=2mm]{standalone}
\usepackage{pgfplots}
\pgfplotsset{compat=1.12}
\usepackage{xifthen}

\pgfmathdeclarefunction{sincf}{1}%
{\pgfmathparse{(abs(#1)<0.01) ? 1 : sin(pi*#1 r)/(pi*#1)}%
}

\pgfmathdeclarefunction{tanny}{1}%
{\pgfmathparse{abs(tan(#1 r))> 1.345 ? 0 : tan(#1 r)}%
}

\begin{document}

\begin{tikzpicture}
    \begin{axis}
        \addplot[domain=-2*pi:2*pi, samples=100, red] {sincf(x)};
        \addplot[domain=-2*pi:2*pi, samples=1000, blue] {tanny(x)};
    \end{axis}  
\end{tikzpicture}

\begin{tikzpicture}
    \begin{axis}
    [   restrict y to domain=-1.345:1.345,
    ]
        \addplot[domain=-2*pi:2*pi, samples=1000, blue, unbounded coords=jump] {tan(x/pi*180)};
        \addplot[domain=-2*pi:2*pi, samples=1000, red, unbounded coords=discard] {tan(x/pi*180-90)};
    \end{axis}  
\end{tikzpicture}

% DOES NOT WORK; DO NOT USE!
%
%\begin{tikzpicture}
%   \foreach \x in {0,10,...,180}
%   { \pgfmathsetmacro{\Length}{\x==90 ? 0 : tan(\x)}
%       \draw (\x/18,0) -- ++ (0,\Length);
%   }
%\end{tikzpicture}

\begin{tikzpicture}
    \foreach \x in {0,10,...,180}
    { \pgfmathsetmacro{\Length}{\ifnum\x=90 0 \else tan(\x)\fi}
        \draw (\x/18,0) -- ++ (0,\Length);
    }
\end{tikzpicture}

\begin{tikzpicture}
    \foreach \x in {0,10,...,180}
    { \ifthenelse{\x=90}
            {   \pgfmathsetmacro{\Length}{0}}
            {   \pgfmathsetmacro{\Length}{tan(\x)}}
        \draw (\x/18,0) -- ++ (0,\Length);
    }
\end{tikzpicture}

\begin{tikzpicture}
    \foreach \x in {0,10,...,180}
    { \pgfmathsetmacro{\RadAngle}{\x/180*pi}
        \pgfmathsetmacro{\GoodAngle}{abs(\RadAngle-pi/2) > 0.01 ? 1 : 0}
        \pgfmathsetmacro{\Length}{\ifnum\GoodAngle=1 tan(\RadAngle*180/pi)\else 0\fi}
        \draw (\x/18,0) -- ++ (0,\Length);
    }
\end{tikzpicture}

\end{document}

相关内容