# Format numbers as a fraction of pi with tikz \pgfmathprintnumber

I'd like to be able to output something like \frac{\pi}{2} for a input number like .5*pi automatically.

I've played a bit with some of the number printing macros available with tikz (closely linked to the fpu library, available at least in the cvs version).

The idea I had is to divide the number by pi, then use the number format/frac and then add a \pi to the result. But it does not work as expected as rounding errors occur. Here is the result for 2*pi, 1.5*pi and 0.5*pi.

Here is what I did so far.

\documentclass{standalone}
\usepackage{tikz,fp}
\usetikzlibrary{fpu}
\makeatletter

% Adapted from the file /generic/pgf/math/pgfmathfloat.code.tex
\pgfkeys{%
/pgf/number format/.cd,
pi/.code = \pgfmath@set@number@printer{pgfmathprintnumber@pi}}

\def\pgfmathprintnumber@pi#1{%
\pgfmathparse{#1/3.141592654}
\pgfmathfloatparsenumber{\pgfmathresult}%
\pgfmathfloatgetfrac{\pgfmathresult}%
\expandafter\pgfmathprintnumber@pi@formatresult\pgfmathresult}

% Nothing really new here, just a copy of
% \pgfmathprintnumber@frac@formatresult only altered to add
% \pi to the result
\def\pgfmathprintnumber@pi@formatresult#1#2#3{%
\begingroup
\pgfkeysgetvalue{/pgf/number format/frac TeX}\pgfmathresult
\toks0=\expandafter{\pgfmathresult}%
\def\pgfmathfloat@loc@TMPa{#1}%
\ifx\pgfmathfloat@loc@TMPa\pgfutil@empty
\ifpgfmathprintnumber@showpositive
\def\pgfmathfloat@loc@TMPa{+}%
\fi
\else
\ifx\pgfmathfloat@loc@TMPa-%
\else
\ifx\pgfmathfloat@loc@TMPa+%
\else
\ifpgfmathprintnumber@showpositive
\edef\pgfmathfloat@loc@TMPa{+\pgfmathfloat@loc@TMPa}%
\fi
\def\pgfmathfloat@loc@TMPb{%
\pgfkeysvalueof{/pgf/number format/frac whole format/.@cmd}}%
\expandafter\pgfmathfloat@loc@TMPb\pgfmathfloat@loc@TMPa\pgfeov
\let\pgfmathfloat@loc@TMPa=\pgfmathresult
\fi
\fi
\fi
\toks1=\expandafter{\pgfmathfloat@loc@TMPa}%
\edef\pgfmathresult{%
\the\toks1 \ifnum#2=0 \else\the\toks0 {#2}{#3}\fi \pi}%
\pgfmath@smuggleone\pgfmathresult
\endgroup
}

\begin{document}

\pgfkeys{/pgf/number format/pi}

\pgfmathprintnumber{2*pi}

\pgfmathprintnumber{1.5*pi}

\pgfmathprintnumber{0.25*pi}

\end{document}

-
If your denominators are always small, you could do something like multiplying with 2520, rounding to the nearest integer, and dividing by 2520 again (in the hope that this doesn't introduce new rounding errors). Note that 2520 is the least common multiple of the numbers 1 to 10; thus this will not work e.g. for pi/11 (but it could work for pi/12, because 12 divides 2520). I don't know if/how this is possible with pgf, however. –  celtschk Feb 20 '12 at 16:54
@celtschk Thanks for the suggestion. However, it does not seem to help with the quick tests I did. –  cjorssen Feb 20 '12 at 17:07
There is one inherent problem of that frac format: it is numerically instable. That means: it will magnify any (small) input errors considerably. See the result of Yiannidis for an illustration (that is inherent to the task, not to the impl.). Since the division by pi will already introduce a rounding error of order 1e-4 or 1e-5, you will always see distortions in the output. Perhaps it is possible to tune the algorithm if you provide additional knowledge (like the expected denominator). –  Christian Feuersänger Feb 26 '12 at 16:05
@ChristianFeuersänger It seems that I underestimated the problem here. Do you have some bibliography to recommend on the subject of decimal to fraction algorithms? –  cjorssen Feb 27 '12 at 13:40
I have no bibliography at hand. In case you find something which improves the task - let me know. –  Christian Feuersänger Feb 27 '12 at 21:00

This is a common problem with decimal to fraction converters in general. Here is a non-TikZ solution, that illustrates the problem:

The algorithm is iterative and finds solutions in various degrees of accuracy, for example in the scan above you can see the "correct solution" is line 3. This is not always so, think for example as to how you would represent the decimal 0.313 in a fractional form, can be anything from 5/16 -> 641/2048 depending on the accuracy you want to achieve.

\documentclass{article}
\usepackage{amsmath,fp}
\begin{document}
\makeatletter
\count@=1
\def\DecimalToFraction#1{
%helper macro
\FPset\zero{0}
\FPset\X{#1}

%% Set initial values
\FPadd\X{\X}{0.0000000001} % avoid overflows and divisions by zero
\FPset\Zi{\X}
\FPset\Di{1}
\FPset\Dprevious{0}
%% begin loop
\loop\ifnum\count@<13
%% numerator term
\FPtrunc\temp{\Zi}{0}
\FPsub\temp{\Zi}{\temp}
%% inverse
\FPdiv\Znext{1}{\temp}
%% Find Dnext
\FPtrunc\IntZnext{\Znext}{0}
%% Di x Int{Zi+1}
\FPmul\temp{\Di}{\IntZnext}
\FPset\Dnext{\temp}
\FPround\Dnext{\Dnext}{0}

%%% Find Ni+1
\FPmul\temp{\X}{\Dnext}
\FPround\temp{\temp}{0}
\FPset\Nnext{\temp}

\FPdiv\ratio{\Nnext}{\Dnext}

$$Z_i=\Znext\to \Nnext/\Dnext =\ratio$$

\FPset{\Dprevious}{\Dnext}
\FPset{\Di}{\Dprevious}
\FPset{\Zi}{\Znext}

\repeat
%% end of loop

\gdef\NUM{\Nnext}
\gdef\DEN{\Dnext}

\makeatother
}

\def\Test#1{%
\DecimalToFraction{#1}
%The number $#1=\frac{\NUM}{\DEN}$
}

\Test{0.25}
\end{document}


The code is based on an algorithm that was programmed using 'C' and was a proof of concept for something else I was working on. No guarantees, just a proof of concept done in moment of madness:) I am sure can be done in Lua much easier.

-
Hum, rather complex problem indeed. The context I have in mind is to automatically put ticks in a pgfplot. Maybe I need to reformulate to narrow my needs. –  cjorssen Feb 21 '12 at 10:06
Indeed, why don't you just scale your plots by Pi? :-) –  krlmlr Feb 21 '12 at 10:20
@cjorssen Sorry I cannot provide a solution to your problem but maybe my answer helps to understanding it better. –  Yiannis Lazarides Feb 21 '12 at 11:38

Substitute \pgfmathparse{#1/3.141592654} by the following code to produce the desired output for the given input:

\pgfmathparse{int(#1*326)/1024}


Note that 326 = ceil(1024 / pi).

It seems to me that \pgfmathfloatgetfrac does not work as expected. I was unable to generate the fraction 1/7, even using the floating-point routines. So, while the above code should work for a large part of multiples of powers of two, don't expect it to work for anything like 1/13.

-
Why did you choose 1024? –  cjorssen Feb 21 '12 at 10:02
Well... My intuition was that keeping it in the powers of two would make the result representable as IEEE floating-point without additional loss from the conversion from fixed to floating. And 1024 was just large enough to work and just small enough to not throw an error during compilation. But I didn't bother deriving a proof or bounds or something. –  krlmlr Feb 21 '12 at 10:18