13

I have been trying to automate the production of stepped tables that contain conversion factors of all sorts. (See Protrusion of fractions in tabulars).

As part of this I have a rather convoluted macro to convert decimals to fractions. The algorithm works fairly well and sample output is shown below:

enter image description here

As I am trying to catch common fractions as those found in traditional units (1/12, 3/4, 1/60, 1/3 etc), I would like to be able to break out of the loop once a limit is reached. I have tested it using FPifgt or similar but I am getting problems with the double fi. Is there a way out of it?

The code is shown below (apologies for length):

\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}
\FPadd\temp{\Di}{\Dprevious}
\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}

\advance\count@ by1
\repeat
%% end of loop

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

\makeatother
}

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

}
\Test{0.375}
\end{document}
yannisl
  • 117,160
  • Why do you want to do something like this inside TeX? Surely a programming language designed for managing numbers would make more sense for this sort of task... – Seamus Mar 02 '11 at 19:26
  • @Seamus Handling numbers through TeX is as easy as any language and you do not need to jump in an out of programs. Problem TeX does not offer a range of datastructures so the problems with if and loops etc. – yannisl Mar 02 '11 at 19:33
  • @Yiannis: TeX just tends to be a bit slow for that kind of things. :) – Bruno Le Floch Mar 02 '11 at 19:36
  • @Yiannis so pick a language with the proper data structures? I'm all for doing stuff in TeX, but it seems like there are easier ways to achieve what you want outside of TeX... – Seamus Mar 02 '11 at 19:37
  • @Seamus, @Yiannis: I have a few ideas of how to code some standard data structures. Which one(s) would be useful? – Bruno Le Floch Mar 02 '11 at 19:51
  • 1
    @Bruno If you can come up with some good link lists and sorting it can be very useful. I have some prototypes for objects. I'll dig them out and post a few questions over the next couple of days. – yannisl Mar 02 '11 at 20:15
  • @Seamus The beauty of combining the calculations - granted is not so easy, when you have recursion etc.. is that you can have "intelligent" documents. At work we do a lot of repetitive reports, calling a few macros resets all the numbers and produces 30 page reports in no time. The example is for a book I am writing so far 80% of all calculations I do as I go along with TeX. It is very similar to literate programming except the code is in the book! – yannisl Mar 02 '11 at 20:21
  • @Yiannis perhaps luatex or at least latex3 would afford more flexibility, and have the same advantages. Or if the numbercrunching got heavier, Sweave and R would be the obvious choice... – Seamus Mar 03 '11 at 16:38
  • @Seamus I agree about LaTeX3, but need to give the Team some more time to stabilize expl3's fp equivalent. For real number crunching I use R and Python. – yannisl Mar 03 '11 at 18:56

2 Answers2

17

To abort the loop after the current iteration simply \let the internal \iterate macro to \relax. If you want to skip the rest of the loop code you can use a macro defined to \fi\iffalse for this (as Bruno already said).


Abort at end of current iteration:

\documentclass{article}

\begin{document}

\newcount\mycount

\mycount=1

\loop\ifnum\mycount<13

 % Do calculation
 \typeout{Loop: \the\mycount}

 \ifnum\mycount>5
    \let\iterate\relax
 \fi
 \advance\mycount by 1\relax
\repeat

\end{document}

Abort immediately:

\documentclass{article}

\def\breakloop{\fi\iffalse}
\begin{document}

\newcount\mycount

\mycount=1

\loop\ifnum\mycount<13

 % Do calculation
 \typeout{Loop: \the\mycount}

 \ifnum\mycount>5
    \expandafter\breakloop
 \fi

 \typeout{ more }
 \advance\mycount by 1\relax
\repeat

\end{document}

Explanation:

First lets look at the (LaTeX) definitions of \loop and \repeat:

\loop:
\long macro:#1\repeat ->\def \iterate {#1\relax \expandafter \iterate \fi }\iterate \let \iterate \relax . 

\repeat:
\fi. 

As you see \loop stores everything between it and \repeat into \iterate which calls itself. This recursion implements the loop. The \expandafter ensures that no dangling \fis get accumulated. As long the loop \if... is true the text is executed, and \iterate is called again after the \fi. If the conditional is false everything until the \fi is skipped including the \expandafter. However if \iterate is changed to \relax the recursion stops independent of the conditional. Because this happens after the \fi no cleanup is required.

The \breakloop generates a \fi\iffalse. The \fi closes the loop conditional and the \iffalse makes TeX skip everything until the final \fi like the loop conditional would do.


If you need to use FP conditionals inside the loop you have to make them "skip save" first. The problem is that FP define own if switches as macros which aren't recognized when TeX skips over an false path. To fix this define macros like this

\def\xFPiflt#1#2{%
  \FPiflt{#1}{#2}%
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
}

Then use \xFPiflt\x\y{<true>}{<false>} instead of \FPiflt\x\y <true> \else <false> \fi.

Martin Scharrer
  • 262,582
  • @Martin thanks. It works nicely with \ifnum but not with the FP conditionals. Any idea? they have the same structure like ifnum. – yannisl Mar 02 '11 at 20:11
  • @Yiannis: Try to move \breakloop to the outside of the FP conditional e.g. by only executing \let\x\breakloop inside that conditional and later simply execute \x (which default to \empty or \relax otherwise). – Martin Scharrer Mar 02 '11 at 20:19
  • @Yiannis: I didn't know much about fp before, but I just saw that they use macros to define own \ifxxx switches which is incompatible with the skipping process of TeX! Can you give an example of a FP conditional inside a \loop? I don't think it will work at all. – Martin Scharrer Mar 02 '11 at 20:21
  • @Yiannis: See my updated answer on how to use FP conditionals inside \loop or \ifnum etc. – Martin Scharrer Mar 02 '11 at 20:34
  • @Martin I would post a new question tomorrow with a minimal, I think it would be better. – yannisl Mar 02 '11 at 20:39
  • @Martin thanks, it worked with a small correction: \xFPiflt{\x\y}{<true>}{<false>}. – yannisl Mar 02 '11 at 20:57
  • @Yiannis: I see. My bad. I mistook the syntax of \FPiflt. I will update the answer. – Martin Scharrer Mar 02 '11 at 21:05
5

The loop has the structure

\loop
...
\if...
...
\repeat

If you are between \if and \repeat, you can issue \fi\iffalse to close the current \if and skip all the text until \repeat (or rather, until a \fi that is produced by \loop...\repeat). This will not work if your loop is \loop...\if...\else...\repeat.

A more robust solution is to place \@gobble\mysentinel just before \repeat, and then use the following macro to go out of the loop:

\def\Iwanttobreakfree#1\mysentinel{\iffalse}

This will throw away all the tokens until \mysentinel, including \@gobble, and the \iffalse makes sure that the loop stops.

  • @bruno thanks, why do you add an argument to the macro Iwanttobreakfree ? – yannisl Mar 02 '11 at 19:42
  • Nice answer (and nice macro name)! – TH. Mar 02 '11 at 19:45
  • @Yiannis: #1 will be all the tokens until \mysentinel: those that you want to get rid of (notice that #1 does not appear in the replacement text of Iwanttobreakfree). – Bruno Le Floch Mar 02 '11 at 19:49
  • @Yiannis: This is how we do loops for expl3, using some slightly different names (\q_recursion_stop is the key marker), with the 'gobble' function having the catchy name \use_none_delimit_by_q_recursion_stop:w :-) – Joseph Wright Mar 02 '11 at 20:03
  • @Joseph: I have to admit that I probably got the idea from reading the expl3 code. – Bruno Le Floch Mar 02 '11 at 20:09
  • 1
    @Bruno: I was not sure - I was more pointed at the name, as the 'use_none' sort-of explains about #1 :-) – Joseph Wright Mar 02 '11 at 20:13
  • I should add that the expl3 approach does not use \iffalse, but then things are all constructed to allow breaking 'from the ground up'. (We don't use Knuth's \loop approach at present.) – Joseph Wright Mar 02 '11 at 20:14
  • @Joseph I just got too busy lately, but it was my new year's resolution to seriously get into LaTeX3:) – yannisl Mar 02 '11 at 20:22
  • @Bruno: What of \let\mysentinel\relax (outside the loop) with \def\Iwanttobreakfree#1\mysentinel#2\repeat{\fi} (inside)? I am a learner! – Ahmed Musa Mar 03 '11 at 14:51
  • @Ahmed: I think that would not work. See Martin Scharrer's answer above; he explains how \loop works, leaving no \repeat behind. So you'd be better off using \iterate rather than \repeat as a delimiter. About \let\mysentinel\relax: that works, but it is not expandable. Not an issue here, but in a better (expandable) version of loops (see expl3), it would be a problem, leaving the \mysentinel in every step. – Bruno Le Floch Mar 05 '11 at 19:46