% This program by D. E. Knuth is not copyrighted and can be used freely. % It was written on 18 Dec 1981 and revised on 24 May 1991. % Here is TeX material that gets inserted after \input webmac \def\PASCAL{Pascal} \font\eightrm=cmr8 \def\title{GLUE} \def\topofcontents{\null \def\titlepage{F} % include headline on the contents page \def\rheader{\mainfont\hfil \contentspagenumber} \vfill \centerline{\titlefont Fixed-Point Glue Setting} \vfill} \def\botofcontents{\vfill \centerline{\hsize 6in\baselineskip9pt \vbox{\eightrm\baselineskip9pt\noindent The preparation of this report was supported in part by the National Science Foundation under grants IST-7921977 and MCS-7723728; by Office of Naval Research grant N00014-81-K-0330; and by the IBM Corporation. `\TeX' is a trademark of the American Mathematical Society.}}} @* Introduction. If \TeX\ is being implemented on a microcomputer that does 32-bit addition and subtraction, but with multiplication and division restricted to multipliers and divisors that are either powers of~2 or positive integers less than~$2^{15}$, it can still do the computations associated with the setting of glue in a suitable way. This program illustrates one solution to the problem. Another purpose of this program is to provide the first ``short'' example of the use of \.{WEB}. @ The program itself is written in standard \PASCAL. It begins with a normal program header, most of which will be filled in with other parts of this ``web'' as we are ready to introduce them. @^program header@> @p program GLUE(@!input,@!output); type @@; var @@; procedure initialize; {this procedure gets things started} var @@; begin @; end; @ Here are two macros for common programming idioms. @d incr(#) == #:=#+1 {increase a variable by unity} @d decr(#) == #:=#-1 {decrease a variable by unity} @* The problem and a solution. We are concerned here with the ``setting of glue'' that occurs when a \TeX\ box is being packaged. Let $x_1$, \dots,~$x_n$ be integers whose sum $s=x_1+\cdots+x_n$ is positive, and let $t$ be another positive integer. These $x_i$ represent scaled amounts of glue in units of sp (scaled points), where one sp is $2^{-16}$ of a printer's point. The other quantity $t$ represents the total by which the glue should stretch or shrink. Following the conventions of \TeX82, we will assume that the integers we deal with are less than $2^{31}$ in absolute value. After the glue has been set, the actual amounts of incremental glue space (in~sp) will be the integers $f(x_1)$, \dots,~$f(x_n)$, where $f$ is a function that we wish to compute. We want $f(x)$ to be nearly proportional to~$x$, and we also want the sum $f(x_1)+\cdots+f(x_n)$ to be nearly equal to~$t$. If we were using floating-point arithmetic, we would simply compute $f(x)\equiv(t/s)\cdot x$ and hope for the best; but the goal here is to compute a suitable~$f$ using only the fixed-point arithmetic operations of a typical ``16-bit microcomputer.'' The solution adopted here is to determine integers $a$, $b$, $c$ such that $$f(x)=\bigl\lfloor 2^{-b}c\lfloor 2^{-a}x\rfloor\bigr\rfloor$$ if $x$ is nonnegative. Thus, we take $x$ and shift it right by $a$~bits, then multiply by~$c$ (which is $2^{15}$ or less), and shift the product right by $b$~bits. The quantities $a$, $b$, and~$c$ are to be chosen so that this calculation doesn't cause overflow and so that $f(x_1)+\cdots +f(x_n)$ is reasonably close to~$t$. The following method is used to calculate $a$ and~$b$: Suppose $$y=\max_{1\le i\le n}\vert x_i\vert\,.$$ Let $d$ and $e$ be the smallest integers such that $t<2^ds$ and $y<2^e$. Since $s$ and~$t$ are less than~$2^{31}$, we have $-30\le d\le31$ and $1\le e\le31$. An error message is given if $d+e\ge31$; in such a case some $x_m$ has $\vert x_m\vert\ge 2^{e-1}$ and we are trying to change $\vert x_m\vert$ to $\vert(t/s)x_m\vert\ge2^{d+e-2}\ge2^{30}$~sp, which \TeX\ does not permit. (Consider, for example, the ``worst case'' situation $x_1=2^{30}+1$, $x_2=-2^{30}$, $t=2^{31}-1$; surely we need not bother trying to accommodate such anomalous combinations of values.) On the other hand if $d+e\le31$, we set $a=e-16$ and $b=31-d-e$. Notice that this choice of~$a$ guarantees that $\lfloor2^{-a}\vert x_i\vert\rfloor<2^{16}$. We will choose~$c$ to be at most~$2^{15}$, so that the product will be less than~$2^{31}$. The computation of $c$ is the tricky part. @^hairy mathematics@> The ``ideal'' value for $c$ would be $\rho=2^{a+b}t/s$, since $f(x)$ should be approximately $(t/s)\cdot x$. Furthermore it is better to have $c$ slightly larger than~$\rho$, instead of slightly smaller, since the other operations in $f(x)$ have a downward bias. Therefore we shall compute $c=\lceil\rho\rceil$. Since $2^{a+b}t/s<2^{a+b+d}=2^{15}$, we have $c\le2^{15}$ as desired. We want to compute $c=\lceil\rho\rceil$ exactly in all cases. There is no difficulty if $s<2^{15}$, since $c$ can be computed directly using the formula $c=\bigl\lfloor(2^{a+b}t+s-1)/s\bigr\rfloor$; overflow will not occur since $2^{a+b}t<2^{15}s<2^{30}$. Otherwise let $s=s_12^l+s_2$, where $2^{14}\le s_1<2^{15}$ and $0\le s_2<2^l$. We will essentially carry out a long division. Let $t$ be ``normalized'' so that $2^{30}\le2^ht<2^{31}$ for some~$h$. Then we form the quotient and remainder of $2^ht$ divided by~$s_1$, $$ 2^ht=qs_1+r_0, \qquad 0\le r_0-s$ we have $q=\lceil2^{h+l}t/s\rceil$; otherwise we can replace $(q,r)$ by $(q\pm1,r\mp s)$ repeatedly until $r$ is in the correct range. It is not difficult to prove that $q$ needs to be increased at most once and decreased at most seven times, since $2^lr_0-qs_2<2^ls_1\le s$ and since $qs_2/s\le(2^ht/s_1)(s_2/2^ls_1)<2^{31}/s_1^2\le8$. Finally, we have $a+b-h-l=-1$ or~$-2$, since $2^{28+l}\le2^{14}s=2^{a+b+d-1}s\le2^{a+b}t< 2^{a+b+d}s=2^{15}s<2^{30+l}$ and $2^{30}\le2^ht<2^{31}$. Hence $c=\lceil2^{a+b-h-l}q\rceil=\lceil{1\over2}q\rceil$ or~$\lceil{1\over4}q\rceil$. An error analysis shows that these values of $a$, $b$, and $c$ work satisfactorily, except in unusual cases where we wouldn't expect them to. @^error analysis@> When $x\ge0$ we have $$\eqalign{f(x)&=2^{-b}(2^{a+b}t/s+\theta_0)(2^{-a}x-\theta_1)-\theta_2\cr &=(t/s)x+\theta_02^{-a-b}x-\theta_12^at/s-2^{-b}\theta_0\theta_1-\theta_2\cr}$$ where $0\le\theta_0,\theta_1,\theta_2<1$. Now $0\le\theta_02^{-a-b}x <2^{e-a-b}=2^{d+e-15}$ and $0\le\theta_12^at/s<2^{a+d}=2^{d+e-16}$, and the other two terms are negligible. Therefore $f(x_1)+\cdots+f(x_n)$ differs from~$t$ by at most about $2^{d+e-15}n$. Since $2^{d+e}$ is larger than $(t/s)y$, which is the largest stretching or shrinking of glue after expansion, the error is at worst about $n/32000$ times as much as this, so it is quite reasonable. For example, even if fill glue is being used to stretch 20 inches, the error will still be less than $1\over1600$ of an inch. @ To sum up: Given the positive integers $s$, $t$, and $y$ as above, we set $$a\gets\lfloor\lg y\rfloor-15,\qquad b\gets29-\lfloor\lg y\rfloor- \lfloor\lg t/s\rfloor,\qquad\hbox{and}\qquad c\gets\lceil2^{a+b}t/s\rceil.$$ The implementation below shows how to do the job in \PASCAL\ without using large numbers. @ \TeX\ wants to have the glue-setting information in a 32-bit data type called |glue_ratio|. The \PASCAL\ implementation of \TeX82 has |glue_ratio =real|, but alternative definitions of |glue_ratio| are explicitly allowed. For our purposes we shall let |glue_ratio| be a record that is packed with three fields: The |a_part| will hold the positive integer |a+16|, the |b_part| will hold the nonnegative integer~|b|, and the |c_part| will hold the nonnegative integer~|c|. When the formulas above tell us to take |b>30|, we might as well set |c:=0| instead, because |f(x)| will be zero in all cases when |b>30|. Note that we have only about 25 bits of information in all, so it should fit in 32 bits with ease. @= @!glue_ratio=packed record @!a_part: 1..31; {the quantity |e=a+16| in our derivation} @!b_part: 0..30; {the quantity |b| in our derivation} @!c_part: 0..@'100000; {the quantity |c| in our derivation} end; @!scaled = integer; {this data type is used for quantities in sp units} @ The real problem is to define the procedures that \TeX\ needs to deal with such |glue_ratio| values: (a)~Given scaled numbers |s|, |t|, and~|y| as above, to compute the corresponding |glue_ratio|. (b)~Given a nonnegative scaled number~|x| and a |glue_ratio|~|g|, to compute the scaled number~|f(x)|. (c)~Given a |glue_ratio|~|g|, to print out a decimal equivalent of |g| for diagnostic purposes. The procedures below can be incorporated into \TeX82 via a change file without great difficulty. A few modifications will be needed, because \TeX's |glue_ratio| values can be negative in unusual cases---when the amount of stretchability or shrinkability is less than zero. Negative values in the |c_part| will handle such problems, if proper care is taken. The error message below should either become a warning message or a call to \TeX's |print_err| routine; in the latter case, an @^error message@> appropriate help message should be given, stating that glue cannot stretch to more than 18~feet long, but that it's OK to proceed with fingers crossed. @*Glue multiplication. The easiest procedure of the three just mentioned is the one that is needed most often, namely, the computation of~|f(x)|. \PASCAL\ doesn't have built-in binary shift commands or built-in exponentiation, although many computers do have this capability. Therefore our arithmetic routines use an array called `|two_to_the|', containing powers of~two. Divisions by powers of two are never done in the programs below when the dividend is negative, so the operations can safely be replaced by right shifts on machines for which this is most appropriate. (Contrary to popular opinion, the operation `|x div 2|' is not the same as shifting |x| right one binary place, on a machine with two's complement arithmetic, when |x| is a negative odd integer. But division {\it is\/} equivalent to shifting when |x| is nonnegative.) @= @!two_to_the: array[0..30] of integer; {$|two_to_the|[k]=2^k$} @ @= @!k:1..30; {an index for initializing |two_to_the|} @ @= two_to_the[0]:=1; for k:=1 to 30 do two_to_the[k]:=two_to_the[k-1]+two_to_the[k-1]; @ We will use the abbreviations |ga|, |gb|, and |gc| as convenient alternatives to \PASCAL's \&{with} statement. The glue-multiplication function |f|, which replaces several occurrences of the `|float|' macro in \TeX82, is now easy to state: @d ga==g.a_part @d gb==g.b_part @d gc==g.c_part @p function glue_mult(@!x:scaled;@!g:glue_ratio):integer; {returns |f(x)| as above, assuming that |x>=0|} begin if ga>16 then x:=x div two_to_the[ga-16] {right shift by |a| places} else x:=x*two_to_the[16-ga]; {left shift by |-a| places} glue_mult:=(x*gc) div two_to_the[gb]; {right shift by |b| places} end; {note that |b| may be as large as 30} @*Glue setting. The |glue_fix| procedure computes |a|, |b|, and |c| by the method explained above. \TeX\ does not normally compute the quantity~|y|, but it could be made to do so without great difficulty. This procedure replaces several occurrences of the `|unfloat|' macro in \TeX82. It would be written as a function that returns a |glue_ratio|, if \PASCAL\ would allow functions to produce records as values. @p procedure glue_fix(@!s,@!t,@!y:scaled; var@!g:glue_ratio); var @!a,@!b,@!c:integer; {components of the desired ratio} @!k,@!h:integer; {$30-\lfloor\lg s\rfloor$, $30-\lfloor\lg t\rfloor$} @!s0:integer; {original (unnormalized) value of |s|} @!q,@!r,@!s1:integer; {quotient, remainder, divisor} @!w:integer; {$2^l$, where $l=16-k$} begin @; if t30) then begin if b<0 then write_ln('! Excessive glue.'); {error message} @^error message@> b:=0; c:=0; {make |f(x)| identically zero} end else begin if k>=16 then {easy case, $s_0<2^{15}$} c:=(t div two_to_the[h-a-b]+s0-1) div s0 {here |1<=h-a-b<=k-14<=16|} else @; end; ga:=a+16; gb:=b; gc:=c; end; @ @= begin a:=15; k:=0; h:=0; s0:=s; while y<@'10000000000 do {|y| is known to be positive} begin decr(a); y:=y+y; end; while s<@'10000000000 do {|s| is known to be positive} begin incr(k); s:=s+s; end; while t<@'10000000000 do {|t| is known to be positive} begin incr(h); t:=t+t; end; end {now $2^{30}\le t=2^ht_0<2^{31}$ and $2^{30}\le s=2^ks_0<2^{31}$, hence $d=k-h$ if $t/s<1$} @ @= begin w:=two_to_the[16-k]; s1:=s0 div w; q:=t div s1; r:=((t mod s1)*w)-((s0 mod w)*q); if r>0 then begin incr(q); r:=r-s0; end else while r<=-s0 do begin decr(q); r:=r+s0; end; if a+b+k-h=15 then c:=(q+1) div 2 @+else c:=(q+3) div 4; end @*Glue-set printing. The last of the three procedures we need is |print_gr|, which displays a |glue_ratio| in symbolic decimal form. Before constructing such a procedure, we shall consider some simpler routines, copying them from an early draft of the program \TeX82. @d unity==@'200000 {$2^{16}$, represents 1.0000} @= @!dig:array[0..15] of 0..9; {for storing digits} @ An array of digits is printed out by |print_digs|. @p procedure print_digs(@!k:integer); {prints |dig[k-1]| \dots |dig[0]|} begin while k>0 do begin decr(k); write(chr(ord('0')+dig[k])); end; end; @ A nonnegative integer is printed out by |print_int|. @p procedure print_int(@!n:integer); {prints an integer in decimal form} var @!k:0..12; {index to current digit; we assume that $0\le n<10^{12}$} begin k:=0; repeat dig[k]:=n mod 10; n:=n div 10; incr(k); until n=0; print_digs(k); end; @ And here is a procedure to print a nonnegative |scaled| number. @p procedure print_scaled(s:scaled); {prints a scaled real, truncated to four digits} var k:0..3; {index to current digit of the fraction part} begin print_int(s div unity); {print the integer part} s:=((s mod unity)*10000) div unity; for k:=0 to 3 do begin dig[k]:=s mod 10; s:=s div 10; end; write('.'); print_digs(4); end; @ Now we're ready to print a |glue_ratio|. Since the effective multiplier is $2^{-a-b}c$, we will display the scaled integer $2^{16-a-b}c$, taking care to print something special if this quantity is terribly large. @p procedure print_gr(@!g:glue_ratio); {prints a glue multiplier} var @!j:-29..31; {the amount to shift |c|} begin j:=32-ga-gb; while j>15 do begin write('2x'); decr(j); {indicate multiples of 2 for BIG cases} end; if j<0 then print_scaled(gc div two_to_the[-j]) {shift right} else print_scaled(gc*two_to_the[j]); {shift left} end; @* The driver program. In order to test these routines, we will assume that the |input| file contains a sequence of test cases, where each test case consists of the integer numbers $t$, $x_1$, \dots,~$x_n$, 0. The final test case should be followed by an additional zero. @= @!x:array[1..1000] of scaled; {the $x_i$} @!t:scaled; {the desired total} @!m:integer; {the test case number} @ Each case will be processed by the following routine, which assumes that |t| has already been read. @p procedure test; {processes the next data set, given |t| and~|m|} var @!n: 0..1000; {the number of items} k:0..1000; {runs through the items} y:scaled; {$\max_{1\le i\le n}\vert x_i\vert$} @!g:glue_ratio; {the computed glue multiplier} @!s:scaled; {the sum $x_1+\cdots+x_n$} @!ts:scaled; {the sum $f(x_1)+\cdots+f(x_n)$} begin write_ln('Test data set number ',m:1,':'); @; @; if s<=0 then write_ln('Invalid data (nonpositive sum); this set rejected.') else begin @; @; end; end; @ @= begin n:=0; repeat incr(n); read(x[n]); until x[n]=0; decr(n); end @ @= begin s:=0; y:=0; for k:=1 to n do begin s:=s+x[k]; if y= begin glue_fix(s,t,y,g); {set |g|, perhaps print an error message} write(' Glue ratio is '); print_gr(g); write_ln(' (',ga-16:1,',',gb:1,',',gc:1,')'); end @ @= begin ts:=0; for k:=1 to n do begin write(x[k]:20); if x[k]>=0 then y:=glue_mult(x[k],g) else y:=-glue_mult(-x[k],g); write_ln(y:15); ts:=ts+y; end; write_ln(' Totals',s:13,ts:15,' (versus ',t:1,')'); end @ Here is the main program. @^main program@> @p begin initialize; m:=1; read(t); while t>0 do begin test; incr(m); read(t); end; end. @*Index. Here are the section numbers where various identifiers are used in the program, and where various topics are discussed.