# pgfplots: Strange Bump in \tanh Function

Follow up question see pgfplots: x vs \x.

I have a rather straightforward pgfplots diagram but I do not understand why there is a bump in my tanh function (should be smooth). The domain (and samples) key seems to influence the outcome. Can you explain this behavior and is there a systematic logic to prevent that (or do I need to use a trial-and-error approach)?

\documentclass[tikz,border=1mm]{standalone}
\usepackage{pgfplots}

\begin{document}

\begin{tikzpicture}
\begin{axis}[
width = 160mm,
height = 90mm,
axis lines = center,
xmin = -10,
xmax = 10,
ymin = -1,
ymax = 1,
enlarge x limits = 0.05,
enlarge y limits = 0.10,
]
color = blue,
mark = none,
smooth,
line width = 1pt,
domain = -30:30, % When changed to -10:10 then the bump is not visible.
samples = 200,
] {tanh(1*\x)};
%
\end{axis}
\end{tikzpicture}

\end{document}


• New bug? Interesting! I would just chalk it up to the poor dancing bear having problems. Aug 22 '21 at 0:20
• Must be fpu's problem. I see tanh(6.656) = 1Y1.2e0]. Aug 22 '21 at 2:09
• I manage to boil this down to (without fpu) \pgfmathdivide@{38.87068}{38.8708}\pgfmathresult = 1.2. Note that the numerator is smaller than the denominator yet the result is > 1. Aug 22 '21 at 2:50
• You can't expect accuracy from pgfmath. (The amazing thing about a dancing bear is that it dances at all.) For accuracy, do you calculations using Matlab etc. and store the data in a pgfplots compatible table. Aug 22 '21 at 14:48
• Also note that the Lua backend is only for pgfplots, as far as I can see it's impossible to switch other parts of TikZ to use Lua. Dec 28 '21 at 2:58

Symbol 1 shows in his answer the root cause of the problem. Maybe missing is the information, that all this (only) happens, when TeX is used as calculation engine. Since Symbol 1 has no idea how to fix this (in TeX) (of course this is no offense) the "fix" is to use another calculation engine ... ;)

% used PGFPlots v1.18.1
\documentclass[border=5pt]{standalone}
\usepackage{pgfmath-xfp}
% define tanh function
\pgfmxfpdeclarefunction{Tanh}{1}{1 - 2/(exp(2*#1) + 1)}
\usepackage{pgfplots}
% use this compat level or higher to use Lua as calculation engine
\pgfplotsset{compat=1.12}
\begin{document}
\begin{tikzpicture}
\begin{axis}[
domain = 6:7,
samples = 200,
no markers,
]
% use TeX as calculation engine
% use Lua as calculation engine (when compiled with LuaLaTeX of course)
% (when you compile with pdfLaTeX or use a lower compat level than given
%  in the preamble, this is equivalent to the previous \addplot)
% use xfp as calculation engine
% use gnuplot as calculation engine
% (assuming gnuplot is installed, available and it is compiled with
%  shell-escape enabled)
\end{axis}
\end{tikzpicture}
\end{document}


• +1: Is there a difference in using tanh(\x) vs tanh(x)? I was not aware of that if that's the case. Aug 22 '21 at 21:50
• Could tanh be made a default function within pgfmath-xfp? Aug 22 '21 at 21:54
• My thought also was that tanh(\x) is the same as tanh(x) but it seems that Lua "doesn't like" "commands", i.e. \<something. But to my experience I never used \x in \addplot calls. Since this is easier to read and easier to type ... But maybe @Symbol1 also has an idea here ;) Aug 23 '21 at 5:43
• Then I think you have to ask the author of the package. But as I understand that it is just a wrapper to make it easy to use the xfp package. So I guess you have to ask the LaTeX3 team if they are willing to add it to xfp. Aug 23 '21 at 5:45
• No, at denominator, you have a minus sign ((exp(2*#1) -1)), it must be a plus sign. With your minus sign, at 0 you obtain infinity. You must obtain 0. Aug 25 '21 at 7:04

tanh is defined to be sinh/cosh. And the unexpected spike is a result of a bug in the division routine. More precisely, the macro \pgfmathdivide@@ defined in pgfmathfunctions.basic.code line 10 (github link) contains an unwanted behavior.

## \pgfmathdivide@@

This macro uses \pgfmath@x (numerator) and \pgfmath@y (denominator) to implement long division. Here is the pseudo code

While (x > δ & y > δ):
If x < y:
append and 0 at the end of \pgfmathresult;
divide y by 10;
Else: %%% x ≥ y
compute a:= floor(x/y);
If a is single digit:
append that digit at the end of \pgfmathresult;
subtract y by x * a
Else: %%% in this case we always have 10 ≤ a < 20
subtract a by 10
depending on how many digits \pgfmathresult has, add 10^-d to \pgfmathresult
append a to the end of \pgfmathresult


You see that the code becomes nasty when x/y ≥ 10. Usually, you would expected that x/y is always less than 10 because the previous y (the y before it was divided by 10) is less than x and the single-digit branch will be executed. So this branch is never executed; it doesn't matter if the code is ugly. But that is not true.

## What could go wrong?

Thanks to finite precision, the following could happen

• (x, y) = (0.00037, 0.00038) so the x < y branch is executed; y is divided by 10.
• now (x, y) = (0.00037, 0.00003) so the x > y branch is executed; now floor(x/y) = 12 ≥ 10

"Fine," you might say. Since 0.00038 -> 0.00003 only happens when \pgfmathresult already has many digits, adding 0.000012 or 0.00009 doesn't really matter. Except that it sometimes does.

You see, adding 10^-d to \pgfmathresult isn't an easy job because 0.001 + 0.999 includes carrying. So instead of textually manipulating \pgfmathresult, this is done by

• converting \pgfmathresult to a TeX dimension (\pgfmath@xa),
• adding 1pt, 0.1pt, ..., or 0.00001pt,
• and then converting TeX dimension back to text.

The problem is, if \pgfmathresult is 0.99999, then the result of adding 0.00001pt to 0.99999pt is 1pt, not 1.00000pt. Recall that we are in the a ≥ 10 branch and we will append a - 10 at the end of the new \pgfmathresult. So if a = 12, we will get 1.2 as the result, not 1.000002.

## Fix

I don't know how to fix this.

• I will call you Sherlock Holmes from now on. :) Aug 22 '21 at 5:16
• Remark: this bug is easy to trigger if you know what you are looking for. For instance 38.89993/38.9=1.2 and 38.8999/38.9=1.1 Aug 22 '21 at 6:59
• @Dr.ManuelKuehner To Sherlock Holmes i give my upvoted. :-) Aug 22 '21 at 16:09
• @Dr.ManuelKuehner Sure! No pressure =) Aug 22 '21 at 22:11
• This reminds me (somewhat) of Scientific Computing SE's What algorithm does (or did?) Excel use for Bessel functions that is discontinuous at x=8? (currently unanswered but some helpful comments)
– uhoh
Aug 23 '21 at 10:39