[Tex/LaTex] How to add, subtract, multiply, and divide in plain TeX

calculationsplain-tex

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?

Best Answer

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