# Ratio arithmetic in plain TeX

I would like to calculate the ratio between two dimensions and produce a dimensionless quantity for use in further calculation in the process. I seem to be unable to do so. Specifically, I run into two strange problems: firstly, as I do any calculation involving \strip@pt, it prints the number. Furthermore, the wrong ratio is calculated, and I'm unsure why.

\catcode@=11

\begingroup
\catcodeP=12
\catcodeT=12
\lowercase{
\def\x{\def\rem@pt##1.##2PT{##1\ifnum##2>\z@.##2\fi}}}
\expandafter\endgroup\x
\def\strip@pt{\expandafter\rem@pt\the}

\def\setdimenzerotofontheightanddepth#1#2{
\dimen0=\fontcharht\font#1
\advance\dimen0 by \fontchardp\font#1
\ifx#2
\else
\advance\dimen0 by \fontchardp\font#2
\fi
}

\setdimenzerotofontheightanddepth fg
\edef\dimzeroamt{\strip@pt\dimen0}

Dimen0 is: \the\dimen0, Baselineskip is: \the\baselineskip

\dimen1=\baselineskip
Unfortunately it also shows the dimzeroamount when I do the calculation: \divide\dimen1 by \dimzeroamt

The ratio is: \strip@pt\dimen1

The real (manually calculated ratio) is $0.90947\dots$
\catcode@=12

\bye

• When you do \divide\dimen0 by <number> the number must be an integer. May 3 '15 at 21:44

Graphics package has code to divide dimens:

\documentclass{article}

\usepackage{graphics}

\makeatletter

\begin{document}

\def\setdimenzerotofontheightanddepth#1#2{
\dimen8=\fontcharht\font#1
\ifx#2
\else
\advance\dimen8 by \fontcharht\font#2
\fi
}

\setdimenzerotofontheightanddepth fg

Dimen8 is: \the\dimen8, Baselineskip is: \the\baselineskip

\Gscale@div\tmp \baselineskip{\dimen8}

gives \tmp

The real (manually calculated ratio) is $0.90947\dots$

\end{document}


Here's a completely plain tex solution. It´s adapted from the graphics package.

\catcode@=11

\begingroup
\catcodeP=12
\catcodeT=12
\lowercase{
\def\x{\def\rem@pt##1.##2PT{##1\ifnum##2>\z@.##2\fi}}}
\expandafter\endgroup\x
\def\strip@pt{\expandafter\rem@pt\the}

\def\setdimenzerotofontheightanddepth#1#2{
\dimen8=\fontcharht\font#1
\advance\dimen8 by \fontchardp\font#1
\ifx#2
\else
\advance\dimen8 by \fontchardp\font#2
\fi
}

\setdimenzerotofontheightanddepth fg%
\edef\dimzeroamt{\strip@pt\dimen8 %
}%

\dimen0=\baselineskip%
\count0=65536
\loop%
\ifdim\dimen0<8192\p@%
\dimen0=2\dimen0%
\divide\count0 by 2 %
\repeat
\divide\dimen8\count0
\divide\dimen0\dimen8
\strip@pt\dimen0
%\edef\x{\strip@pt\dimen0}

The real (manually calculated ratio) is $0.90947\dots$%
\catcode@=12

\bye


The syntax rules of TeX tell you that you can

\divide\dimen0 by <number>


where <number> is an integer. In your case, the expansion of \dimzeroamt is 13.19443, so TeX duly divides \dimen0 by 13 and prints .19443.

You can do it with expl3 (also in Plain TeX):

\input expl3-generic

\ExplSyntaxOn
\cs_new_protected:Nn \ioiooiioio_getfactor:Nnnn
{% #1 is a control sequence, #2 the dimension, #3 and #4 two characters
\tl_set:Nx #1
{
\fp_eval:n
{
\dim_to_fp:n { #2 } /
\dim_to_fp:n
{
\fontcharht\font#3 + \fontcharht\font#4
+
\fontchardp\font#3 + \fontchardp\font#4
}
}
}
}
\cs_set_eq:NN \getfactor \ioiooiioio_getfactor:Nnnn
\ExplSyntaxOff

\getfactor\test{\baselineskip}{f}{g}

\test

\bye


I'm not sure you want to sum those dimensions; in case you want to use the maximum between heights and depths, change the lines in the second \dim_to_fp:n command to

       \dim_max:nn { \fontcharht\font#3 } { \fontcharht\font#4 }
+
\dim_max:nn { \fontchardp\font#3 } { \fontchardp\font#4 }

• So dividing by a real number is a little more involved.... May 3 '15 at 21:51
• @1010011010 Yes, but it's doable also with Plain TeX (with the help of expl3) May 3 '15 at 22:24
• with the help of expl3... or other packages too ;-)
– user4686
Jul 28 '17 at 12:57

The classical TeX has limited arithmetic, so the division of decimal numbers is processed by division by integers and the optimum of precision is found via loop with multiple two (see the answer by 1010011010).

But, if you are using eTeX (this is common extension of TeX today) then you can utilize the fact that the integer arithmetic is 64bit when \numexpr is processed. So the division is more simple. Roughly speaking:

 2^16 * \number\dividend / \number\divisor


is the new integer number which can be interpreted as dimen when sp unit is appended. This dimen could be printed using \the as desired decimal result and you can remove the trailing pt. The whole construction looks more complicated but all was mentioned here:

\newdimen\fnheight
\fnheight=13.19443pt  \baselineskip=12pt

{\lccode\?=\p \lccode\!=\t  \lowercase{\gdef\ignorept#1?!{#1}}}

\expandafter\ignorept\the
\dimexpr\the\numexpr 65536*\number\baselineskip/\number\fnheight sp\relax


Of course, we have limited precision to 16<point>16 bits numbers. If we need more precision (but it isn't needed in common cases) then we can use one of various macro packages for doing arithmetic. For example my apnum.tex does the calculation with arbitrary precision using only classical TeX primitives:

\input apnum

\newdimen\fnheight
\fnheight=13.19443pt  \baselineskip=12pt

\evaldef\OUT{\number\baselineskip / \number\fnheight}

\OUT  % \OUT=.90947485284083681234 because \apFRAC=20 by default.

\bye


The answer by @wipet explains how to use e-TeX. Variant using same method:

\makeatletter
\newcommand\SetToRatio[3]{% sets #1 to be the ratio #2/#3, where #2 and #3
% are lengths (registers or expressions).
% The ratio #2/#3 should evaluate to less than 16384 in absolute value to
% avoid arithmetic overflow. It will be computed as fixed point
% number with about 4 or 5 digits after decimal mark.
\edef #1%
{\strip@pt\dimexpr
\numexpr\dimexpr#2\relax*65536/\dimexpr#3\relax\relax sp\relax}%
}
\makeatother


Notice however that this approach has the defect that the result, the ratio R, can not be say 20000 because 20000pt is bigger than the TeX maximal dimension, hence can not be produced by the \dimexpr. Thus we can't do

\SetToRatio{\foo}{20000sp}{1sp}


Although the arguments are perfectly legal, the result 20000 can not be produced.

In another answer of mine https://tex.stackexchange.com/a/328894/4686, I wrote a pure Plain TeX macro which does not have this defect, and also it is more precise than the Graphics macro.

The answer contains variants differing about the final rounding, here is variant D. I use LaTeX document for easying up testing by site visitors. The macro is actually called \divdef, not \SetToRatio. It uses no package.

% j'avais fait cela dans https://tex.stackexchange.com/a/328894/4686

% Octobre 2016

\documentclass{article}

\makeatletter

\countdef\ddf@cnta=\z@
\countdef\ddf@cntb=\tw@
\countdef\ddf@cntc=4
\countdef\ddf@cntd=6

% % if used with Plain TeX, un-comment this
% % LaTeX loop or any loop allowing \else\repeat:
%
% \long\def\loop #1\repeat{%
%     \def \iterate {#1\relax \expandafter \iterate \fi}\iterate
%     \let \iterate \relax }

% \def\divdef #1#2#3{% if using plain tex

\newcommand\divdef [3]{%
% description:
% computes R = #2/#3 as nearest multiple of 1/65536 (ties go to even)
% then define the macro #1 to be the decimal expansion of this up to five digits
% after decimal mark.
\begingroup
\dimen@ #3\relax   % denominator
\dimen@ii #2\relax % numerator
\ifdim\dimen@<\z@
\dimen@-\dimen@
\dimen@ii-\dimen@ii
\fi
\ifdim\dimen@ii<\z@
\def\ddf@sgn{-}\dimen@ii-\dimen@ii
\else
\let\ddf@sgn\empty % no \@empty in Plain !
\fi
\ddf@cnta\dimen@   % non negative denominator (we hope non zero...)
\ddf@cntb\dimen@ii % non negative numerator
\divide\ddf@cntb\ddf@cnta % integer part of ratio, will be stored in \ddf@cntd
\ddf@cntd\ddf@cntb
\multiply\ddf@cntb-\ddf@cnta % no overflow possible because TeX's division truncates
\advance\ddf@cntb\dimen@ii   % now numerator in \ddf@cntb is < denom
\count@\z@ % will store fractional part as a multiple of sp's
\ifnum\ddf@cntb>\z@
\ifnum\ddf@cnta>32768\relax
\ddf@cnta65536\relax
\loop
\ddf@cntc\dimen@ % denominator
\ifnum\ddf@cntb<\ddf@cntc
\divide\ddf@cnta\tw@
\else
\ifnum\ddf@cntb=\ddf@cntc
\divide\ddf@cnta\tw@
\ddf@cnta\z@ % abort the loop here
\else
\advance\count@\ddf@cnta % not same order as in previous branch!
\divide\ddf@cnta\tw@
\ddf@cnta-\ddf@cnta
\ddf@cntb\ddf@cntc
\fi
\fi
\ifnum\ddf@cnta=\z@\else % signed quantity: can not do if foo>\z@ ...
\repeat
% it is possible here that \count@ is 65536
% in case of a tie at the last unit the rounding was to even!
\else
% here denom <= 2^15=32768 (=0.5pt), hence 65536num <= 2^31 - 65536
\multiply\ddf@cntb65536\relax
% extra steps to do rounding
\ddf@cntc\ddf@cnta
\divide\ddf@cntc\tw@
\advance\ddf@cntb\ddf@cntc % no overflow possible
\ddf@cntc\ddf@cntb % need to keep copy for later branch
\divide\ddf@cntc\ddf@cnta
\count@\ddf@cntc
\ifodd\ddf@cnta
% odd denom, no tie possible
\else
\multiply\ddf@cntc\ddf@cnta
\ifnum\ddf@cntb=\ddf@cntc
% implement "ties go to even", the rounding was "up"
\fi
\fi
% to get \count@ 65536 we would need to have N/D >= 65535.5/65536
% i.e. N/D >= 1 - 1/131072, but N/D<= 1 - 1/D, D<=32768, hence
% despite the rounding this branch always produces \count@ < 65536.
\fi
\fi
\dimen@\count@ sp\relax
% (\the\count@) % debug check
\expandafter\divdef@end\the\dimen@ #1%
}
\begingroup
\catcodeP 12
\catcodeT 12
\lowercase{\gdef\divdef@end #1.#2PT}#3{%
\advance\ddf@cntd #1\relax % almost always #1=0
\ifnum#2>\z@
\edef\x{\endgroup\edef\noexpand#3{\ddf@sgn\the\ddf@cntd.#2}}%
\else
\ddf@cntd\ddf@sgn\ddf@cntd
\edef\x{\endgroup\edef\noexpand#3{\the\ddf@cntd}}%
\fi
\x
}\endgroup

\begin{document}

\ttfamily

\divdef\FOO{20000sp}{1sp}

\meaning\FOO

\divdef\FOO{355pt}{113pt}

\meaning\FOO

\divdef\FOO{1pt}{7pt}

\meaning\FOO

\divdef\FOO{10000pt}{7pt}

\meaning\FOO

\divdef\FOO{1000000000sp}{7sp}

\meaning\FOO

\end{document}


As you can see particularly on the last example this goes beyond the possibilities of other approaches.