PGF math engine imprecise?

There are different math engines that can be used with pgf/TikZ.

Library fixedpointarithmetic

\documentclass[11pt]{minimal}
\usepackage{tikz}
\usetikzlibrary{calc}
\usepackage{fp}
\usetikzlibrary{fixedpointarithmetic}
\begin{document}
\begin{tikzpicture}[fixed point arithmetic]
    \foreach \x in {120,121,...,150}
    {
        \pgfmathparse{1/\x}\let\fraction\pgfmathresult
        \pgfmathparse{asin(\fraction)}\let\angle\pgfmathresult
        \node at (0, \x/2-60) {\x, \fraction, \angle};
    }
\end{tikzpicture}
\end{document}

fixedpointarithmetic


Yes it's imprecise (unless you use the fpu library) and also the engine doesn't compute value-by-value but instead uses a look-up table. But besides that TeX math capabilities are limited anyway. So you can also fake an approximation for yourself.

\documentclass[tikz]{standalone}
\begin{document}
\begin{tikzpicture}
    \foreach \x[count=\xi] in {120,...,150}
    {
        \pgfmathsetmacro\myfraction{1/\x}
        \pgfmathsetmacro\myangle{asin(\myfraction)}
        \pgfmathsetmacro\modifiedangle{deg(\myfraction + 1/6*(\myfraction)^3 + 3/40*(\myfraction)^5)}
        \node[text width=3cm,align=left] (a-\xi) at (0,0.4*\xi) {\texttt{\myangle,\modifiedangle}};
    }
\end{tikzpicture}
\end{document}

asin vs. approximated asin

enter image description here


I recently found out that arctangent seems to be well approximated by a continued fraction on the interval [-1,1] (the rest of the real line reduces to that using arctan(1/x)=pi/2-arctan(x)). Here is a quick and dirty implementation of arctangent using l3fp. It seems to have at least 15 digits of accuracy, but there may be bugs.

\documentclass{article}
\usepackage{expl3, xparse}
\ExplSyntaxOn

%%%%% Defining the function atan2, such that atan2(y,x) is the angle of x+iy.
\int_const:Nn \c__atan_steps_int { 30 } % must be even
\fp_new:N \l__atan_x_fp
\fp_new:N \l__atan_y_fp
\fp_new:N \l__atan_offset_fp
\fp_new:N \l__atan_tmp_fp
\fp_new:N \l__atan_result_fp
\cs_new_protected:Npn \atan:nnN #1#2#3
  {
    \fp_set:Nn \l__atan_y_fp {#1}
    \fp_set:Nn \l__atan_x_fp {#2}
    \fp_zero:N \l__atan_offset_fp
    \fp_compare:nT { \l__atan_y_fp < - \l__atan_x_fp }
      {
        \fp_set:Nn \l__atan_x_fp { - \l__atan_x_fp }
        \fp_set:Nn \l__atan_y_fp { - \l__atan_y_fp }
        \fp_set_eq:NN \l__atan_offset_fp \c_pi_fp
      }
    \fp_compare:nT { \l__atan_y_fp > \l__atan_x_fp }
      {
        \fp_set_eq:NN \l__atan_tmp_fp \l__atan_x_fp
        \fp_set_eq:NN \l__atan_x_fp \l__atan_y_fp
        \fp_set:Nn \l__atan_y_fp { - \l__atan_tmp_fp }
        \fp_add:Nn \l__atan_offset_fp { pi / 2 }
      }
    % Now X >= 0 and -X <= Y <= X.
    \fp_compare:nTF { \l__atan_y_fp < 0 }
      {
        \fp_set:Nn \l__atan_y_fp { - \l__atan_y_fp }
        \__atan_compute:
        \fp_set:Nn #3 { \l__atan_offset_fp - \l__atan_result_fp }
      }
      {
        \__atan_compute:
        \fp_set:Nn #3 { \l__atan_offset_fp + \l__atan_result_fp }
      }
  }
\cs_new_protected_nopar:Npn \__atan_compute:
  {
    % atan(y/x)
    % = (y/x)(1-y^2/(3x^2+9y^2/(5+4y^2/(7x^2+25y^2/(9+16y^2/(11x^2+...))))))
    %
    \fp_set:Nn \l__atan_x_square_fp { \l__atan_x_fp * \l__atan_x_fp }
    \fp_set:Nn \l__atan_y_square_fp { \l__atan_y_fp * \l__atan_y_fp }
    \fp_set:Nn \l__atan_result_fp
      { ( 2 * \c__atan_steps_int + 3 ) * \l__atan_x_square_fp }
    \int_step_inline:nnnn \c__atan_steps_int { -2 } { 2 }
      {
        \fp_set:Nn \l__atan_result_fp
          {
            (2 * ##1 - 1) * \l__atan_x_square_fp
            + (##1 + 1)**2 * \l__atan_y_square_fp
              / ( 2 * ##1 + 1
                  + ##1 * ##1 * \l__atan_y_square_fp / \l__atan_result_fp )
          }
      }
    \fp_set:Nn \l__atan_result_fp
      {
        \l__atan_y_fp / \l__atan_x_fp
        * ( 1 - \l__atan_y_square_fp / \l__atan_result_fp )
      }
  }
%%%%% End of atan2.
\fp_new:N \l__asin_x_fp
\cs_new_protected:Npn \asin:nN #1#2
  {
    \fp_set:Nn \l__asin_x_fp {#1}
    \atan:nnN
      { \l__asin_x_fp }
      { 1 + (1 - \l__asin_x_fp ** 2) ** .5 }
      \l__asin_x_fp
    \fp_set:Nn #2 { 2 * \l__asin_x_fp }
  }
%%%%%
\fp_new:N \X
\NewDocumentCommand{\showasin}{m}
  {
    \fp_gset:Nn \X {#1}
    \fp_to_tl:N \X
    &
    \asin:nN { \X } \X
    \fp_to_tl:N \X
    \\
  }
\ExplSyntaxOff
\begin{document}

\begin{tabular}{cc}
  \showasin { 1 }
  \showasin { 1 - 1e-16 }
  \showasin { 1 - 1e-12 }
  \showasin { 1 - 1e-8 }
  \showasin { 1 - 1e-4 }
  \showasin { .9 }
  \showasin { .1 }
  \showasin { 1e-4 }
  \showasin { 1e-8 }
  \showasin { 1e-12 }
  \showasin { 1e-20 }
  \showasin { 1e-40 }
  \showasin { -1e-40 }
  \showasin { -.123 }
  \showasin { -1 }
\end{tabular}

\end{document}