44

I find many cases where I need to calculate many numbers, using adding, subtracting, multiplying, and dividing, but it would save much time if TeX could simply compute these for me.

This is a sample situation, which can calculate how many minutes or hours spend in lessons and how many of those minutes are spent in the laboratory.

\def\weeks{20} % term is twenty weeks
\def\lessonsperweek{3} % meets three times per week
\def\lessonduration{60} % meets for sixty minutes

\def\percentlectures{.5} % half the time is spent in lectures
\def\percentdiscussions{.2} % 20 percent of the time is spent in disucssions
\def\percentlab{1 - \percentdiscussions - \percentlectures} % remaining percent of time left to lab work

\def\totalduration{\lessonduration * \lessonsperweek * \weeks} % minutes spent in lessons

\def\totaldurationhours{\totalduration / 60}

\def\labduration{\totalduration * \percentlabs} % minutes spent in the lab

Total time in lab: \labduration minutes.

Total minutes in class: \totalduration minutes.

Total hours in class: \totaldurationhours hours.

\def\weeks{19} % these two values are changed later in the document
\def\lessonsperweek{2}

Total minutes in class: \totalduration minutes. % this value would be different because of the chagnes to \weeks and \lessonsperweek

When I compiled this, +, -, *, and / were just displayed in the document as symbols. How can I let TeX calculate these values?

5 Answers 5

45

You can do arithmetic (with +, -, *, /, but no ^ for powers) using \numexpr expressions.

The \numexpr expressions are among the e-TeX extensions to the Knuth's TeX.

(e-TeX extensions: on modern installations they are activated by default, except if you use the executable named tex on the command line)

However you can't use truly fractional numbers inside \numexpr...\relax (for example doing 1/7+1/3). And / does therein a (rounded) division to the nearest integer. Integers must be between -2147483647 and +2147483647: this is the same limitation as for the integers one can store in a TeX \count or in a LaTeX counter; and a \count can be used directly inside a \numexpr expression, for a LaTeX counter one has \value{mycountername}.

I wrote package xintexpr which can be used either in Plain TeX (\input xintexpr.sty) or in LaTeX (\usepackage{xintexpr}).

  1. if you want the final result rounded to an integer use \xintiexpr, for example \xintiexpr 123456789987654321/(2^32+3^20)\relax. Else \xintexpr will compute a fraction.

  2. if you want the fraction in irreducible form, use the reduce function: \xintexpr reduce (1/1+1/2+1/3+1/4+1/5+1/6+1/7+1/8+1/9+1/10)\relax.

  3. contrarily to \numexpr which can be used directly in places where TeX expect a (whole) number, for example in an \ifnum test, an \xintiexpr needs to be prefixed by \xintthe: \xintthe\xintiexpr (or \xinttheiexpr). But naturally, the produced number should be less than the 2^31 limit.

  4. an \xintthe\xintexpr (or, shorter \xinttheexpr) can not be used in an \ifnum test, as TeX does not understand fractions. The package provides tests of its own to compare numbers incl. fractions.

  5. an \xintexpr expression must be terminated by a \relax whereas a \numexpr will terminate legally on any token (like a dot .) not expected by its syntax.

  6. \numexpr -(1+2)\relax does not work! but \xintexpr -(1+2)\relax does...

  7. \xintexpr is completely expandable.

Hence things such as:

\message{\xinttheexpr reduce (1/1+1/2+1/3+1/4+1/5+1/6+1/7+1/8+1/9+1/10)\relax}
\message {\xinttheiexpr 123456789987654321/(2^32+3^20)\relax}

are possible.

Code for the OP use case:

\input xintexpr.sty\relax % compile with etex or pdftex

% macro to use \xinttheiexpr which will round to the nearest whole number.
\def\roundandprint #1{\xinttheiexpr #1\relax }

\def\weeks{20}          % term is twenty weeks
\def\lessonsperweek{3}  % meets three times per week
\def\lessonduration{60} % meets for sixty minutes

\def\percentlectures{.5}    % half the time is spent in lectures
\def\percentdiscussions{.2} % 20 percent of the time is spent in disucssions

% remaining percent of time left to lab work:
\def\percentlab{\xintexpr 1 - \percentdiscussions - \percentlectures\relax} 

% minutes spent in lessons:
\def\totalduration{\xintexpr \lessonduration * \lessonsperweek * \weeks\relax} 

% total duration in hours:
\def\totaldurationhours{\xintexpr \totalduration / 60 \relax}

% minutes spent in the lab:
\def\labduration{\xintexpr \totalduration * \percentlab \relax} 

Total time in lab: \roundandprint{\labduration} minutes.

Total minutes in class: \roundandprint{\totalduration} minutes.

Total hours in class: \roundandprint{\totaldurationhours} hours.

\def\weeks{19} % these two values are changed later in the document
\def\lessonsperweek{2}

Total minutes in class: \roundandprint{\totalduration} minutes. % this value
                                % would be different because of the chagnes to
                                % \weeks and \lessonsperweek

\bye

add-subtract-multiply

Remark: the various \xintexpr ... \relax in \percentlab etc... are a bit optional; they could have been replaced by parentheses, but using such \xintexpr ... \relax sub-expressions provides the maximal flexibility. For example, one can embed them in an \edef, naturally if everything involved in the computation is defined at that time.

As mentioned in a prior example the ^ for powers is accepted: but the exponent must be an integer (3^(10/2) is ok for \xintexpr, which will correctly compute 3^5). There is a sqrt function, which computes the square-root with, by default, 16 digits of precision. A second optional argument allows more precision:

\xinttheexpr sqrt(2,60)\relax

gives 60 digits of precision. To get the result in scientific notation, there is:

\xintthefloatexpr sqrt(2,60)\relax
1
  • I forgot to say that this answer does not use \count registers; and that it is provided in case you need to use (as in your mwe) stuff such as 0.2 or 0.5 which can not be used inside a \numexpr.
    – user4686
    Sep 14, 2013 at 16:38
26

plain tex (uniquely) is usually used with the classic Tex engine (or at least with pdf and e-tex extensions disabled) so there is no infix arithmetic

\newcount\zzz
\zzz=5
\multiply\zzz by 3
\advance\zzz by 2

sets \zzz to 17. If you use the plain format with e-tex you can use e-tex infix arithmentic

\newcount\zzz
\zzz=\numexpr 5*5 + 2\relax
1
  • 1
    @jfbu For assigning a value to a counter, there's no need of \number or \the in front of \numexpr. Actually, \count255=\number\numexpr1+1\relax could give you problems; try \count255=\the\numexpr1+1\relax 1\showthe\count255 and \count255=\numexpr1+1\relax 1\showthe\count255
    – egreg
    Sep 14, 2013 at 20:17
10

You can use PGFmath.

For \pgfmathprintnumber from (pgfmathfloat.code.tex) it is necessary to define \pgf@texdist@protect for reasons unknown to me (this seems to be some protection mechanism).
Simply \inputting pgf.tex works, too.

Code

\expandafter\def\csname pgf@texdist@protect\endcsname{}%
\input pgfmath.tex
\def\weeks{20}          % term is twenty weeks
\def\lessonsperweek{3}  % meets three times per week
\def\lessonduration{60} % meets for sixty minutes

\def\percentlectures{.5}    % half the time is spent in lectures
\def\percentdiscussions{.2} % 20 percent of the time is spent in disucssions
                            % remaining percent of time left to lab work
\def\percentlab{1 - \percentdiscussions - \percentlectures}
                            % minutes spent in lessons
\def\totalduration{\lessonduration * \lessonsperweek * \weeks}
\def\totaldurationhours{(\totalduration) / 60}
\def\labduration{(\totalduration) * (\percentlab)} % minutes spent in the lab

\pgfset{
  number format/.cd,
  min exponent for 1000 sep=4,
  int detect,
}
Total time in lab: \pgfmathprint{\labduration} (\pgfmathprintnumber[fixed,precision=0]{\pgfmathresult}) minutes.

Total minutes in class: \pgfmathprint{\totalduration}
                 (\pgfmathprintnumber{\pgfmathresult}) minutes.

Total hours in class: \pgfmathprint{\totaldurationhours} 
               (\pgfmathprintnumber{\pgfmathresult}) hours.

\def\weeks{19}         % these two values are changed later in the document
\def\lessonsperweek{2}
                       % minutes spent in lessons

% this value would be different because of the chagnes to \weeks and \lessonsperweek
Total minutes in class: \pgfmathprint{\totalduration}
                 (\pgfmathprintnumber{\pgfmathresult}) minutes.
\bye

Output

enter image description here

4
  • I upvoted, but have a question: as shown by the value for \labduration, pgfmath gives approximate values; does it provide a macro rounding to the nearest integer? (to pretty-print the result).
    – user4686
    Sep 14, 2013 at 19:39
  • @jfbu As always, pgfmath uses simple TeX dimen calculation so the approximation is as precise as TeX gets it (with dimens and \dimexpr). Even though PGF also provides the fpu library we can use \pgfmathprintnumber to pretty-print numbers. Though the loading doesn’t seem as easy as I expected. Sep 14, 2013 at 20:05
  • nice, thanks. I guess the fpu library has some round function and this is what you refer too (excuse my ignorance...)
    – user4686
    Sep 14, 2013 at 20:08
  • @jfbu Not really: “The floating point unit (fpu) allows the full data range of scientific computing for use in PGF. Its core is the PGF math routines for mantissa operations, leading to a reasonable trade–of between speed and accuracy. It does not require any third–party packages or external programs.” It is heavily used by pgfplots and was written by the same author. The fpu library wouldn’t have these inaccurate results in the first place. Sep 19, 2013 at 12:21
5

If you are able to use latex, then you should consider the sagetex package, which gives you the power of a computer algebra system (CAS). It's using Python/Sage commands but since you're using such basic operations it will be easy for anyone to follow; easier than the code you wrote, I think, because Python is a high level language. I didn't see a calculation for percenttests so I set it to .3

\documentclass{article}
\usepackage{sagetex}
\begin{document}
\begin{sagesilent}
weeks=20
lessonsperweek=3
lessonduration=60
percentlectures=.5
percentdiscussions=.2
percenttests=.3
percentlabs=1-percenttests-percentlectures
totalduration=lessonduration*lessonsperweek*weeks
totaldurationhours=totalduration/60
labduration=totalduration*percentlabs
\end{sagesilent}
\noindent Total time in lab: $\sage{labduration.n(digits=5)}$ minutes.
Total minutes in class: $\sage{totalduration.n(digits=5)}$.
Total hours in class: $\sage{totaldurationhours}$ hours.
\begin{sagesilent}
weeks = 19
lessonsperweek = 2
\end{sagesilent}
Total minutes in class: $\sage{totalduration}$ minutes.
\end{document}

The operations are done in the background using sagesilent. To access the calculations just insert \sage along with the calculation you wanted. You can control the digits displayed with adding .n(digits=5) or whatever you want. Previously, sagetex relied on having Sage installed on your computer. Now, with Sagemath Cloud, you can run your LaTeX in the cloud and get access to Sage, too, without needing it installed on your computer. Screenshot of the the code running below: enter image description here

5
  • 3
    sagetex is great, the OP asked for use with Plain: can one use sagetex with Plain or is it only for LaTeX?
    – user4686
    Sep 14, 2013 at 17:33
  • 3
    the OP seems to want to have macros expanding to variable possible inputs, e.g. the \weeks macro; how does one go about feeding sage with such inputs?
    – user4686
    Sep 14, 2013 at 17:38
  • It's for latex; I adjusted my answer to indicate that fact. The code above modifies weeks with the second sagesilent session modifying weeks.
    – DJP
    Sep 14, 2013 at 20:38
  • Is this part of the standard distribution for TeX Live? I don't seem to have sagetex.
    – A.Ellett
    Sep 15, 2013 at 15:39
  • Sagetex is part of TeX Live says "One potentially confusing issue is...TeXLive 2009 includes SageTeX. This may seem nice, but...it’s important...Sage bits and LaTeX bits be synchronized–which is a problem...since both Sage and SageTeX are updated frequently, and TeXLive is not.". In order to use sagetex you've needed (until Sagemath cloud) Sage installed on your computer. Since most people have an internet connection, no reason to limit yourself to plain Tex; you get Sage/sagetex functionality,too.
    – DJP
    Sep 15, 2013 at 16:57
4

If you want to subtract from a counter, use a negative value:

\newcount\h
\h 12
\advance\h -1

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .