Changes for MP.WEB by Andreas Scherer, February 22, 1995. @x In the C version, there are external routines that use double precision floating point to simulate functions such as |make_fraction|. This is carefully done to be virtually machine-independent and it gives up to 12 times speed-up on machines with hardware floating point. Since some machines do not have fast double-precision floating point, we provide a C preprocessor switch that allows selecting the standard versions given below. @p ifdef('FIXPT')@/ function make_fraction(@!p,@!q:integer):fraction; var @!f:integer; {the fraction bits, with a leading 1 bit} @!n:integer; {the integer part of $\vert p/q\vert$} @!negative:boolean; {should the result be negated?} @!be_careful:integer; {disables certain compiler optimizations} begin if p>=0 then negative:=false else begin negate(p); negative:=true; end; if q<=0 then begin debug if q=0 then confusion("/");@;@+gubed@;@/ @:this can't happen /}{\quad \./@> negate(q); negative:=not negative; end; n:=p div q; p:=p mod q; if n>=8 then begin arith_error:=true; if negative then make_fraction:=-el_gordo@+else make_fraction:=el_gordo; end else begin n:=(n-1)*fraction_one; @; if negative then make_fraction:=-(f+n)@+else make_fraction:=f+n; end; end;@/ endif('FIXPT') @y In the C version for Motorola 68020 machines, there are external routines written in assembly language that use features of the processor to simulate functions such as |make_fraction|. @p function make_fraction(@!p,@!q:integer):fraction; var @!negative:boolean; {should the result be negated?} begin if p>=0 then negative:=false else begin negate(p); negative:=true; end; if q<=0 then begin debug if q=0 then confusion("/");@;@+gubed@;@/ @:this can't happen /}{\quad \./@> negate(q); negative:=not negative; end; if p div q >=8 then begin arith_error:=true; if negative then make_fraction:=-el_gordo@+else make_fraction:=el_gordo; end else begin if negative then make_fraction:=-make_f(p,q) else make_fraction:=make_f(p,q); end; end; @z @x @p ifdef('FIXPT')@/ function take_fraction(@!q:integer;@!f:fraction):integer; var @!p:integer; {the fraction so far} @!negative:boolean; {should the result be negated?} @!n:integer; {additional multiple of $q$} @!be_careful:integer; {disables certain compiler optimizations} begin @=0| and |q>0|@>; if f; be_careful:=n-el_gordo; if be_careful+p>0 then begin arith_error:=true; n:=el_gordo-p; end; if negative then take_fraction:=-(n+p) else take_fraction:=n+p; end;@/ endif('FIXPT') @y @ The ``inner loop'' routine |take_fraction| is externally implemented in assembly language for speed. @^inner loop@> @z @x @ The invariant relations in this case are (i)~$\lfloor(qf+p)/2^k\rfloor =\lfloor qf_0/2^{28}+{1\over2}\rfloor$, where $k$ is an integer and $f_0$ is the original value of~$f$; (ii)~$2^k\L f<2^{k+1}$. @^inner loop@> @= p:=fraction_half; {that's $2^{27}$; the invariants hold now with $k=28$} if q @z @x Once again it is a good idea to use a machine-language replacement if possible; otherwise |take_scaled| will use more than 2\pct! of the running time when the Computer Modern fonts are being generated. @^inner loop@> @y @z @x @p ifdef('FIXPT')@/ function take_scaled(@!q:integer;@!f:scaled):integer; var @!p:integer; {the fraction so far} @!negative:boolean; {should the result be negated?} @!n:integer; {additional multiple of $q$} @!be_careful:integer; {disables certain compiler optimizations} begin @=0| and |q>0|@>; if f; be_careful:=n-el_gordo; if be_careful+p>0 then begin arith_error:=true; n:=el_gordo-p; end; if negative then take_scaled:=-(n+p) else take_scaled:=n+p; end;@/ endif('FIXPT') @y @p function take_scaled(@!q:integer;@!f:scaled):integer; var @!p:integer; {the fraction so far} @!negative:boolean; {should the result be negated?} @!n:integer; {additional multiple of $q$} @!be_careful:integer; {disables certain compiler optimizations} begin @=0| and |q>0|@>; if f0 then begin arith_error:=true; n:=el_gordo-p; end; if negative then take_scaled:=-(n+p) else take_scaled:=n+p; end; @z @x @ @= p:=half_unit; {that's $2^{15}$; the invariants hold now with $k=16$} @^inner loop@> if q=0 then negative:=false else begin negate(p); negative:=true; end; if q<=0 then begin debug if q=0 then confusion("/");@+gubed@;@/ @:this can't happen /}{\quad \./@> negate(q); negative:=not negative; end; n:=p div q; p:=p mod q; if n>=@'100000 then begin arith_error:=true; if negative then make_scaled:=-el_gordo@+else make_scaled:=el_gordo; end else begin n:=(n-1)*unity; @; if negative then make_scaled:=-(f+n)@+else make_scaled:=f+n; end; end;@/ endif('FIXPT') @y @p function make_scaled(@!p,@!q:integer):scaled; var @!negative:boolean; {should the result be negated?} begin if p>=0 then negative:=false else begin negate(p); negative:=true; end; if q<=0 then begin debug if q=0 then confusion("/");@+gubed@;@/ @:this can't happen /}{\quad \./@> negate(q); negative:=not negative; end; if p div q >=@'100000 then begin arith_error:=true; if negative then make_scaled:=-el_gordo@+else make_scaled:=el_gordo; end else begin if negative then make_scaled:=-make_s(p,q) else make_scaled:=make_s(p,q); end; end; @z @x @ @= f:=1; repeat be_careful:=p-q; p:=be_careful+p; if p>=0 then f:=f+f+1 else begin double(f); p:=p+q; end; until f>=unity; be_careful:=p-q; if be_careful+p>=0 then incr(f) @y @z