17.2. The save instruction gives ˜?™ a fresh meaning, hence ˜?™ is a numeric variable

unconnected to any other variables. When the group ends and ˜?™ is restored to its

old meaning, the value of the group expression no longer has a name. (It™s called a

“capsule” if you try to show it.) Therefore the value of the group expression is a new,

nameless variable, as desired.

17.3. It™s a nameless pair whose xpart and ypart are equal; thus it is essentially

equivalent to ˜whatever — (1, 1)™.

248 Appendix A: Answers to All the Exercises

17.4. ˜v3 := begingroup save ?; picture ?; ? endgroup™ refreshes the picture special-purpose

...

variable v3 without changing other variables like v2 . This construction works also for

pairs, pens, strings, etc.

18.1. Yes; the direction at z.j will be either left or right .

18.2. beginlogochar("A",15);

x1=.5w;

x2=x4=leftstemloc;

x3=x5=w-x2;

top y1=h+o;

(Figure A18a will be inserted here;

y2=y3=barheight; too bad you can™t see it now.)

bot y4=bot y5=-o;

draw z4--z2--z3--z5;

super_half(2,1,3);

labels(1,2,3,4,5);

endchar;

Notice that all three calls of super_half in logo.mf are of the form ˜super half (2, j, 3)™.

But it would not be good style to eliminate parameters i and k, even though super_half

is a special-purpose subroutine; that would make it too too special.

18.3. If bracket = 0 or serif darkness = 0. (It™s probably not a good idea to make

serif darkness = 0, because this would lead to an extreme case of the ˜. . .™ triangle,

which might not be numerically stable in the presence of rounding errors.) Another

case, not really desirable, is left jut = right jut = 0.

18.4. That™s a strange question. The serif routine includes a penpos that de¬nes

z$l , z$ , and z$r relative to each other, and it de¬nes the other six points relative to

them. Outside the routine the user ought to specify just one x coordinate and one

y coordinate, in order to position all of the points. This can be done either before or

after serif is called, but has an easier job if it™s done beforehand.

18.5. Yes; see the previous exercise. (But in the program for "A" it™s necessary to

de¬ne y4l and y5r , so that theta 4 and theta 5 can be calculated.)

18.6. beginchar ("H", 13u#, ht #, 0);

x1 = x2 = x5 = 3u;

x3 = x4 = x6 = w ’ x1 ;

y1c = y3c = h; y2c = y4c = 0;

serif (1, thick , ’90, jut , jut );

serif (2, thick , 90, jut , jut ); (Figure A18b will be inserted here; too bad

you can™t see it now.)

serif (3, thick , ’90, jut , jut );

serif (4, thick , 90, jut , jut );

¬ll serif edge 2

- - reverse serif edge 1 - - cycle;

¬ll serif edge 4

- - reverse serif edge 3 - - cycle;

penpos5 (thin , 90); penpos6 (thin , 90);

y5 = y6 = .52h; penstroke z5e - - z6e ;

penlabels(1, 2, 3, 4, 5, 6); endchar.

Appendix A: Answers to All the Exercises 249

18.7. def top serif (su¬x $)(expr xx , theta , left jut , right jut ) = future pen

penpos$ (xx , 0); z$a ’ z$l = z$f ’ z$r = (bracket /abs sind theta ) — dir theta ;

y$c = y$d = y$ ; x$c = x$l ’ left jut ; x$d = x$r + right jut ;

z$b = z$l + whatever — dir theta = z$c + whatever — dir ’phi ;

z$e = z$r + whatever — dir theta = z$d + whatever — dir phi ;

labels($a, $b, $c, $d, $e, $f ) enddef ;

def top serif edge su¬x $ =

(z$a . . controls z$b . . z$c

- - (¬‚ex (z$c , .5[z$c , z$d ] ’ dishing , z$d )) shifted (0, +epsilon )

- - z$d . . controls z$e . . z$f ) enddef ;

18.8. Assuming that py = 0, the e¬ective right stroke weight would be px ·sin(θ5 ’φ)

if it were drawn with one stroke of broad pen , and xxx · sin θ5 is the additional weight

corresponding to separate strokes xxx apart. The right-hand side of the equation is

the same calculation in the case of vertical strokes (θ = 90—¦ ), when the stroke weight

of "I" is considered. (Since a similar calculation needs to be done for the letters K,

V, W, X, Y, and Z, it would be a good idea to embed these details in another macro.)

18.9. beginchar ("H", 13u#, ht #, 0);

x1 = x2 = x5 = 3u;

x3 = x4 = x6 = w ’ x1 ;

y1 = y3 = h; y2 = y4 = 0;

top serif (1, xx , ’90, jut , jut );

bot serif (2, xx , 90, jut , jut ); (Figure A18c will be inserted here; too bad

top serif (3, xx , ’90, jut , jut );

you can™t see it now.)

bot serif (4, xx , 90, jut , jut );

¬lldraw bot serif edge 2

- - reverse top serif edge 1 - - cycle;

¬ll bot serif edge 4

- - reverse top serif edge 3 - - cycle;

y5 = y6 = .52h; draw z5 - - z6 ;

penlabels(1, 2, 3, 4, 5, 6); endchar.

18.10. The replacement text contains ten tokens,

t e p

def = enddef ; def t =

where t , e , and p are placeholders for argument insertion. When this macro is

expanded with tracingmacros > 0, will type

foo(TEXT0)<expr>of<primary>->def(TEXT0)=(EXPR1)enddef;def.t=(EXPR2)

followed by the arguments (TEXT0), (EXPR1), and (EXPR2).

18.11. According to the rule just stated, the ¬rst comma is an abbreviation for ˜}} {{™.

Hence the ¬rst argument is a capsule containing the value of x; the second is the text

˜(,™ ; the third is the text ˜(}})™.

18.12. This snares future pens before they™re converted to pens, because pickup

wants to yscale by aspect ratio before ellipses change to polygons.

250 Appendix A: Answers to All the Exercises

18.13. The construction ˜hide ( statement list )™ expands into ˜gobble begingroup vacuous

endfor

statement list ; endgroup™, so the argument to gobble must be evaluated. The

show

begingroup causes to start executing statements. When that has been str

done, the ¬nal statement turns out to be empty , so the argument to gobble turns

out to be a vacuous expression (cf. Chapter 25). Finally, gobble ™s replacement text is

empty, so the hidden text has indeed disappeared. (The hide macro in Appendix B is

actually a bit more e¬cient, but a bit trickier.)

™s “stomach” would see ˜;™ if mag is known, but there would

19.1. Then

be no change if mag is unknown. An extra semicolon is harmless, since

statements can be empty . But it™s wise to get in the habit of putting ˜;™ before ¬,

because it saves a wee bit of time and because ˜;™ de¬nitely belongs before endfor.

19.2. No; that would be shocking.

1

19.3. Yes, if and only if n ’ is an even integer. (Because ambiguous values are

2

rounded up.)

19.4. No.

19.5. def even = not odd enddef .

19.6. The ¬rst is 5, because the pair is not considered to be a path. The second and

third are 0, because the pair is forced to become a path.

19.7. (a) The loop text is never executed. (b) It™s executed only once, for x = 1.

(c) It™s executed in¬nitely often, for x = 1, 1, 1, . . . . (d) Since ten times -

™s internal representation of .1 is slightly larger than 1, the answer is not what

you probably expect! The loop text is executed for x = 0, 0.1, 0.20001, 0.30002,

0.40002, 0.50003, 0.60004, 0.70004, 0.80005, and 0.90005 only. (If you want the values

(0, .1, .2, . . . , 1), say ˜ for xx = 0 upto 10: x := xx /10; text endfor™ instead.)

19.8. m = 1, n = 0.

19.9. def exitunless expr b = exitif not b enddef . (The simpler alternative

˜def exitunless = exitif not enddef ™ wouldn™t work, since ˜not™ applies only to the

following primary .)

19.10. numeric p[]; boolean n_is_prime; p[1]=2; k:=1;

for n=3 step 2 until infinity:

n_is_prime:=true;

for j=2 upto k: if n mod p[j]=0: n_is_prime:=false; fi

exitif n/p[j]<p[j]; endfor

if n_is_prime: p[incr k]:=n; exitif k=30; fi

endfor fi

show for k=1 upto 30: str p[k]&"="&decimal p[k], endfor "done" end.

19.11. ˜0; exitif true;™.

20.1. False; consider ˜a1(2)™.

20.2. A value very close to z2 .

Appendix A: Answers to All the Exercises 251

20.3. vardef lo_cube(expr x)=x*x*x<10 enddef; **

tolerance

show solve lo_cube(0,10), 10**1/3; end.

str

(EXPR

With the default tolerance of 0.1, this will show the respective values 2.14844 and (SUFFIX

(TEXT

2.1544. A more general routine could also be written, with ˜10™ as a parameter:

at sharp

at

vardef lo_cube[](expr x)=x*x*x<@ enddef;

sharp at

show solve lo_cube10(0,10); origin

skyline

if we ask for minimum tolerance (tolerance := epsilon ), the result is 2.15445; the true histogram

value is ≈ 2.15443469.

20.4. begingroup(p5dx,p5dy)endgroup.

20.5. Say ˜first#@™ after de¬ning ˜vardef first.a[]@#=@ enddef™. (There are

other solutions, e.g., using substrings of str #@, but this one is perhaps the most

instructive.)

20.6. The machine answers thus:

incr=macro:<suffix>->

begingroup(SUFFIX2):=(SUFFIX2)+1;(SUFFIX2)endgroup

z@#=macro:->begingroup(x(SUFFIX2),y(SUFFIX2))endgroup

Parameters to a macro are numbered sequentially, starting with zero, and classi¬ed as

either (EXPRn ), (SUFFIXn ), or (TEXTn ). In a vardef, (SUFFIX0) and (SUFFIX1) are

always reserved for the implicit parameters #@ and @; (SUFFIX2) will be @#, if it is used

in the parameter heading, otherwise it will be the ¬rst explicit parameter, if it happens

to be a su¬x parameter.

20.7. secondarydef t transum tt =

begingroup save T; transform T;

for z=origin,up,right:

z transformed t + z transformed tt = z transformed T; endfor

T endgroup enddef.

21.1. False; about twice as often (2/3 versus 1/3).

21.2. 1+floor uniformdeviate n.

21.3. A random point on the straight line segment from z1 to z2 . (The point z1

itself will occur with probability about 1/65536; but point z2 will never occur.)

21.4. A random “skyline” texture, 100 pt wide — 10 pt tall:

The density decreases uniformly as you go up in altitude.

21.5. A more-or-less bell-shaped histogram:

22.1. (a) I¬ n is an integer between 0 and 255. (b) I¬ s is a string of length 1.

22.2. Whoever says that there™s no such primitive operation has forgotten about

scantokens.

252 Appendix A: Answers to All the Exercises

22.3. vardef octal primary n = Hobby

save m,s; m:=abs round n; string s; s=decimal(m mod 8);

forever: m:=m div 8; exitif m=0;

s:=decimal(m mod 8) & s; endfor

s enddef;

˜str[m mod 8]™ could also be used instead of ˜decimal(m mod 8)™.

23.1. Point (x, y) is the upper left corner, (x + c1 ’ c0 , y) is the upper right corner,

(x, y ’ r1 + r0 ) is the lower left corner, and (x + c1 ’ c0 , y ’ r1 + r0 ) is the lower right

corner. (Pixels outside this rectangle will not be displayed.)

23.2. Rede¬ne openit so that it puts the top left at (’50, 280).

23.3. (This routine is due to John Hobby.)

newinternal n_windows; % the number of windows allocated so far

newinternal screen_bot; % the first untouched screen row

pair screen_corner; % the upper left corner of next window

def wipescreen = % do this to initialize or reinitialize

for i:=1 upto n_windows: display blankpicture inwindow i; endfor

n_windows := screen_bot := 0; screen_corner := origin enddef;

wipescreen;

vardef new_window@#(expr u,v) = save r,c,up_lft; pair up_lft;

if n_windows=15: errmessage "No more windows left"

else: window@# := incr n_windows;

up_lft = (min(xpart u,xpart v), max(ypart u, ypart v));

(r,c) = (u+v-2up_lft) rotated 90;

if ypart screen_corner + c > screen_cols:

screen_corner:=(screen_bot,0); fi

openwindow window@# from screen_corner

to screen_corner+(r,c) at up_lft;

screen_bot := max(screen_bot,xpart screen_corner + r);

screen_corner := screen_corner + (0,c) fi; enddef;

24.1. The entire path now has negative y coordinates except at point (0, 0), so

the outline of the ¬lled region is (0, ’1) - - (10, ’1) - - (10, 0) - - (0, 0) - - (0, 1) - - cycle.

(Notice that the digitized outline actually goes up to (0, 1) before coming straight down

again. This ¬lls no pixels, but correctly puts “cancelling” edges from (0, 0)

to (0, 1) and back to (0, 0) into its edge structure, because the point (0, .5) is on the

boundary and rounds to (0, 1).)

24.2. The horizontal tangents are already taken care of by the equations top y1 =

h + o and bot y4 = ’o, so nothing needs to be done there. We should, however, say

x2 = w ’ x3 = good.x (1.5u + s)

so that vertical tangents will occur in good places. Since w is an integer, and since the

logo pen has left-right symmetry, w ’ x3 will be good if and only if x3 is.

24.3. Let b be the pen breadth. Then .5w is a good x value if and only if lft .5w is

an integer; but lft .5w = .5w ’ .5b, and this is an integer if and only if w ’ b is even.

Appendix A: Answers to All the Exercises 253

24.4. There are no ambiguous points on the outlines of this stroke, except perhaps draw

¬‚oor

on the top and bottom edges; the latter can occur only if round py is odd. Hence

there is always left-right symmetry, but top-bottom symmetry might fail because of a

missing row at the bottom (e.g., when px = py = 3). In a case like the ˜ ™ we do have

both symmetries, because y1 and x4 are in good positions.

24.5. No matter where you place the octagon so that it isn™t touching any ambiguous

points, exactly seven ambiguous points are inside it; hence every one-point draw ¬lls

exactly seven pixels. (In fact, you always get one of the patterns , , , or .)

24.6. f = .5(x4 ’x3 ); the desired equation is ˜x1 ’x2 = round(x1 ’x2 +.5(x4 ’x3 ))™.

24.7. Let x3 = n + 1 + θ, where n is an integer and 0 ¤ θ < 1. By drawing lines of

2

slope 30—¦ from the pixel centers, we ¬nd that there are three cases for the rightmost

four columns:

Case A, ; Case B, ; Case C, .

√ √ √

Case A occurs for 0 ¤ θ < 2 3 ’ 3; Case B occurs for 2 3 ’ 3 ¤ θ < 3 ’ 1; Case C

√

occurs for 3 ’ 1 ¤ θ < 1. The tip in Case A looks a bit too sharp, and Case C looks

too blunt, so Case B seems best. This case occurs when x3 is near an integer, so it™s

OK to let x3 be an integer.

√ √

24.8. Let y1 = n + θ. If θ lies between 1 3 ’ 1 and 1 3 + 1 , the top row after

6√ √

2 2 2

digitization will contain two black pixels. If θ lies between 1 3 + 1 and 5 3 ’ 1 , we

6 2 6 2

get the desired shape. Otherwise we get ˜ ™.

√

24.9. (We choose θ = 1 3 in the previous exercise, since this is the midpoint of the

2

desirable interval.) The equations are changed to

x1 = x2 = w ’ x3 = round s;

y3 = .5 + ¬‚oor .5h;

z1 ’ z2 = (z3 ’ z2 ) rotated 60;

y1 := .5 sqrt 3 + round(y1 ’ .5 sqrt 3);

y2 := h ’ y1 ;

and then we ¬ll z1 - - z2 - - z3 - - cycle as before.

24.10. vardef vround primary v =

¬‚oor(v — aspect ratio + .5)/aspect ratio enddef ;

vardef good.y primary y =

vround (y + pen top ) ’ pen top enddef .

24.11. (m + 1/2, (n + 1/2)/aspect ratio ). These are the points that currenttransform

maps into pixel centers.

25.1. By looking at the syntax rules, we ¬nd, for example,

boolean expression true

numeric expression 0

pair expression (0,0)

path expression makepath pencircle

pen expression nullpen

254 Appendix A: Answers to All the Exercises

picture expression nullpicture delimiter

Missing token has been inserted

string expression ""

capsule

transform expression Impossible!

vacuous expression begingroup endgroup

Every transform expression includes either a variable or a capsule. Incidentally, there

are some amusing alternative 5-token solutions for pair expression :

postcontrol 0 of makepath nullpen

makepath pencircle intersectiontimes makepath nullpen

26.1. The responses are

> a=left delimiter that matches ::

> b=(outer) a

> c=a

because: a has been rede¬ned from internal quantity to delimiter; b is still an internal

quantity (named a), and it has been stamped outer; c denotes the same internal

quantity, but it hasn™t got outerness.

27.1. We want to delete

0 [ 1 + sqrt 43 ]

from the sequence of tokens that is about to read next, in order to get

rid of the right bracket, which we can see is going to be just as erroneous as the left

bracket was. However, there is another way to proceed (and indeed, this alternative

would be preferable to counting tokens, if the bracketed expression were longer): We

could simply delete 2 tokens, then ˜I(™. This would produce another error stop,

! Missing ˜)™ has been inserted.

<to be read again>

]

<*> show round[1 + sqrt43]

;

?h

I found no right delimiter to match a left one. So I™ve

put one in, behind the scenes; this may fix the problem.

?

after which it™s easy to delete the ˜]™ and continue successfully.

27.2. looked ahead, to see if the expression being evaluated was going

to be something like ˜round 0[1+sqrt43,x]™. But when it found no comma, it put

back several tokens so that they could be read again. (The subexpression 1+sqrt43

had already been evaluated, so a “capsule” for its value, 7.55743, was inserted among

the tokens to be reread.) The expression ended with ˜0™, and ˜round 0™ was shown.

Then found extra tokens following the show command; a semicolon should

have come next. To continue, the user should just plunge ahead recklessly once again,

letting delete those unwanted tokens.

Appendix A: Answers to All the Exercises 255

27.3. The little program counterclockwise

SHAKESPEARE

path p,q; p=flex((-32,481),(-42,455),(-62,430)); KNUTH

q=flex((-62,430),(-20,452),(42,448));

show p intersectiontimes q, p intersectionpoint q,

angle -direction 2 of p, angle direction 0 of q; end

gives the following results:

>> (1.88403,0.07692)

>> (-59.32149,432.59523)

>> 43.14589

>> 45.47263

(Actually, the paths would also cross if 452 were 451, but it™s such a close call that

doesn™t call the path strange; prefers to turn counterclockwise

—¦

when the amount of turn is close enough to 180 , even if it™s slightly more.)

27.4. If this exercise isn™t just a joke, the title of this appendix is a lie. (When

you™ve solved this exercise you might also try to ¬nd all the lies and/or jokes that are

the same in both this book and The TEXbook.)