home *** CD-ROM | disk | FTP | other *** search
- Unit RealFast;
-
- Interface
- Uses Crt;
-
- TYPE RealRec = record case byte of
- 1 : (R : real);
- 2 : (E : byte);
- 3 : (F : array [1..5] of byte;
- S : byte);
- end; {record}
-
- DoubleRec = Record
- case byte of
- 1 : (R : real);
- 2 : (F1 : array [1..6] of byte;
- E : integer);
- 3 : (F2 : array [1..7] of byte;
- S : byte);
- end;
-
- FUNCTION Intel8087Present : Boolean;
- {Returns TRUE if machine has a 80x87 Processor }
-
- PROCEDURE MulDiv (VAR Input : RealRec; N : integer);
- {Multiplies or divides (depending on the sign of 'N') the R
- component of 'Input' by two to the power 'N'. Generates a
- run-time error if a multiplication would cause an overflow.
- Protection is also provided against underflow. }
-
- PROCEDURE MulDiv87 (VAR Input : DoubleRec; N : integer);
- {This is an 8087 version of MulDiv. }
-
-
- (* Note: Version 4.0 changes the name of the 8087 version 3.0 real to
- Double; real now stands the 6 byte real regardless of whether the
- program was compiled to work with the 8087.
- {NORTHWESTERN UNIVERSITY TURBO USERS GROUP UTILITIES}
-
- (** NUtility Fast Floating Point Operations **)
-
- {(C) J. E. Hilliard 1986}
-
-
- {/TEN MICROSECOND MULTIPLICATION, DIVISION AND NEGATION OF REAL NUMBERS
- ---------------------------------------------------------------------
-
- A ten microsecond floating-point multiplication, division and nega-
- tion on a plain PC? Yes, but (surprise) there is a catch. As Henry
- Ford might have put it: 'You can multiply or divide by any number you
- want so long as it is a power of two'. This condition is not quite so
- restrictive as it might first appear, because several commonly used
- numerical techniques such as the Simplex procedure for curve fitting
- and fast Fourier transforms require just that operation. With respect
- to negation (changing the sign) there are no restrictions.
-
- The trick is an old one in assembly language programming and in-
- volves, as you will probably have guessed, manipulating the exponent
- of the number. Floating point numbers are stored as two fields: a
- mantissa and an exponent. In addition, one bit is used to define the
- sign of the mantissa. Since the number is in binary format, adding an
- integer N to the exponent corresponds to a multiplication of the
- number by two to the power N. Similarly, a substraction yields a
- division. Negation is performed by flipping the sign bit.
-
- Because only integer arithmetic is involved, these operations are
- very fast as compared with the built in floating-point commands. The
- implementation of these operations depends on the number storage
- format which differs for the 8087 and non-8087 versions of Turbo
- Pascal. We will therefore consider the two versions separately.
-
-
- Non-8087 Version
- ----------------
- In this version real numbers occupy six bytes. The first holds the
- exponent and the remainder the mantissa which, following the usual
- topsy-turvy convention, is stored with the most significant byte
- last. The first bit of this byte is the sign bit. A convenient way of
- accessing the exponent byte and the sign bit is to define a variant
- record type: /}
-
- (* TYPE RealRec = record case byte of
- 1 : (R : real);
- 2 : (E : byte);
- 3 : (F : array [1..5] of byte;
- S : byte);
- end; {record} *)
-
- {/Being a variant record, the E byte overlays the first byte (ie. the
- exponent) of the number R. The only purpose of component F is to
- place component S in the byte holding the sign bit. It should be
- noted that a variable of type RealRec occupies no more space than a
- variable defined as real and, somewhat surprisingly, the executions
- times for normal operations on the R component are identical with
- those for a real variable. Thus nothing is sacrificed by using this
- declaration.
-
- The following illustrates a multiplication by two:
-
- VAR DataPoint : RealRec;
-
- begin
- DataPoint.R := 1.11111;
- DataPoint.E := succ (DataPoint.E);
- write (DataPoint.R);
- end.
-
- [Note: The succ () command takes up less memory and executes faster
- than the numerically equivalent: DataPoint.E + 1.] For division by
- two the succ () command is replaced by pred (). Obviously, adding 2,
- 3, 4 . . to the exponent is equivalent to multiplications by 4, 16,
- 64 . . and similarly for divisions.
-
- The above coding provides no protection against overflow (ie. incre-
- menting the exponent beyond the allowable maximum). If such protec-
- tion is required the following should be substituted for the second
- line:
-
- if DataPoint.E = $FF
- then
- DataPoint.R := Exp (1E3)
- else
- DataPoint.E := succ (DataPoint.E);
-
- If executed, the third line will generate a run-time error 01.
-
- Protection against underflow when dividing by two is simpler because
- no error message is required:
-
- if DataPoint.E <> 0 then
- DataPoint.E := pred (DataPoint.E);
-
- To change the sign it is only necessary to OR the sign bit; thus:
-
- DataPoint.S := DataPoint.S or $80;
-
-
- 8087 Version
- ------------
-
- This version uses an 8087 format known as Real8 which is 8 bytes
- long. The first 6 bytes are devoted to the mantissa. The exponent and
- the sign bit occupy parts of the last two bytes and are stored as
- follows:
-
- Byte 7 Byte 8
- E E E E M M M M S E E E E E E E
-
- in which E, M and S denote exponent, mantissa and sign bits respec-
- tively. (The reason for this odd packing is for compatibility with a
- shorter 8087 format, Real4). The appropriate variant record for this
- format is: /}
-
- (* DoubleRec = Record
- case byte of
- 1 : (R : real);
- 2 : (F1 : array [1..6] of byte;
- E : integer);
- 3 : (F2 : array [1..7] of byte;
- S : byte);
- end; *)
-
- {/Again, the only purpose of F1 and F2 is to position the other compo-
- nents. Unlike the non-8087 case, the E component is not actually the
- exponent but is related to it by:
-
- Exponent = (DataPoint.E shr 4) and $7FF,
-
- in which the $7FF mask is required to eliminate the sign bit. It is
- important to note that since E is declared as an integer, Bytes 7 and
- 8 will be reversed. It therefore follows that a multiplication by two
- requires adding 10 hex to E as shown in the following example:
-
- VAR DataPoint : DoubleRec;
-
- Begin
- DataPoint.R := 1.1111;
- DataPoint.E := DataPoint.E + $10;
- write (DataPoint.R);
- End.
-
- For protection against overflow when multiplying by two the coding
- should be changed to:
-
- if (DataPoint.E shr 4) and $7FF = $7FF
- then
- DataPoint.R := Exp (1E3)
- else
- DataPoint.E := DataPoint.E + $10;
-
- and, for protection against underflow when dividing by two:
-
- if (DataPoint.E shr 4) and $7FF <> 0 then
- DataPoint.E := DataPoint.E - $10;
-
- The code for a change in sign is identical to that for the non-8087
- version.
-
- At a considerable sacrifice in execution speed, the multiplication
- and division routines can be replaced by a call to one of the MulDiv
- procedures listed at the end. A function Compiled87 is also included
- for checking which version of Turbo was used for compiling the
- source.
-
- TIMINGS
- -------
- The execution times listed in the Table are for an Intel 8088
- processor running at 4.77 Mhz. All times are in microsec. and were
- determined by executing the code 10,000 times. The time required was
- measured with a resolution of one ms using the JTimer function.
- Corrections were applied for the time required for executing a null
- loop and for accessing the clock. The times are rounded to three
- significant figures.
-
- It will be seen that without range checking a 10 to 13 microsec.
- multiplication or division by two can be achieved. This is some 125
- times faster than normal in the plain version and 25 times faster in
- the 8087 version. Although using one of the MulDiv procedures is more
- convenient and saves space, there is a large penalty to be paid in
- speed. Most of the extra time required is spent calling the procedure
- and in parameter passing. In fact, if the 8087 Turbo version were as
- fast as it should and could be, MulDiv87 would probably be slower
- than a regular multiplication.
-
- With respect to changing the sign, flipping the sign bit is 10 to 13
- times faster than the conventional instruction R := - R.
-
-
- TABLE
- -----
- Execution Times in Microsec.
-
- ------------------------------------------------------
- Turbo V3 Plain 8087
- ------------------------------------------------------
-
- In-code multiply and
- divide by 2 (NRC) 10.5 12.5
-
- In-Code multiply by 2 (WRC) 25.9 35.9
-
- In-Code divide by 2 (WRC) 23.1 32.1
-
- Procedure MulDiv(87) 177.0 200.0
-
- Regular multiplication 1320.0 283.0
-
- Regular division 1890.0 306.0
-
- Fast negation (flip bit) 13.0 13.0
-
- Regular negation (R := - R) 127.0 180.0
-
- ------------------------------------------------------
- Notes : NRC - No Range Check for underflow or overflow.
- WRC - With Range Check.
- ------------------------------------------------------
-
- LISTINGS
- -------- /}
-
- Implementation
-
- FUNCTION Intel8087Present : Boolean;
-
- {Returns TRUE if machine has a 80x87 Processor }
-
- Begin
-
- Intel8087Present := False;
-
- {$IFDEF CPU87}
- Intel8087Present := True;
- {$ENDIF}
-
- End; {Intel8087Present : Boolean}
-
-
- PROCEDURE MulDiv (VAR Input : RealRec; N : integer);
-
- {Multiplies or divides (depending on the sign of 'N') the R
- component of 'Input' by two to the power 'N'. Generates a
- run-time error if a multiplication would cause an overflow.
- Protection is also provided against underflow. }
-
- Begin
-
- if N > 0 {Multiplication. }
- then
- if Input.E + N >= $FF
- then
- Input.R := Exp (1E3) {Generate run-time error 01. }
- else
- Input.E := Input.E + N
- else {Division. }
- if Input.E + N < 0
- then {Reduce Input.E to zero. }
- while Input.E > 0 do
- Input.E := pred (Input.E)
- else
- Input.E := Input.E + N;
-
- End; {MulDiv (VAR Input : RealRec; N : integer)}
-
-
- PROCEDURE MulDiv87 (VAR Input : DoubleRec; N : integer);
-
- {This is an 8087 version of MulDiv. }
-
- Begin
-
- if N > 0 {Multiplication. }
- then
- if (Input.E shr 4) and $7FF + N >= $7FF
- then
- Input.R := Exp (1E3) {Generate run-time error 01. }
- else
- Input.E := Input.E + (N shl 4)
- else {Division. }
- if (Input.E shr 4) and $7FF + N < 0
- then {Reduce exponent to zero. }
- while (Input.E shr 4) and $7FF > 0 do
- Input.E := Input.E - $10
- else
- Input.E := Input.E + (N shl 4);
-
- End; {MulDiv87 (VAR Input : DoubleRec; N : integer)}
-
-
-
-
- PROCEDURE RealFastDemo;
-
- {This demo serves the following purposes: (1) Shows the coding
- required for the two versions of TURBO. (2) Gives confirmation
- that the operations function as they should. (3) Provides a
- convincing, if only qualitative, demonstration of the speed of
- the fast procedures. NOTE: This procedure can be compiled with
- either the non-8087 or 8087 versions without any changes. }
-
- VAR
-
- Data : RealRec;
- Data87 : DoubleRec;
- R : Real;
- J : integer;
- Ch : Char;
-
- Begin
-
- ClrScr;
- GoToXY (29, 3); write ('REALFAST DEMONSTRATION');
- GoToXY (29, 4); write ('----------------------');
- GoToXY (1, 7);
- if not Intel8087Present
- then {Non-8087 version. }
- begin
- writeln ('Non-8087 Version':30);
- writeln;
- Data.R := 1.111111111;
- writeln (Data.R:30, 'Starting number':30);
- Data.E := succ (Data.E); {X 2. }
- writeln (Data.R:30, 'Fast multiplication by two':30);
- Data.S := Data.S or $80; {Change sign. }
- writeln (Data.R:30, 'Fast change of sign':30);
- Data.E := Data.E - 2; {Divide by 4. }
- writeln (Data.R:30, 'Fast division by four':30);
- writeln;
- R := 1.234567;
- writeln;
- writeln (' Press any key to start 3000 REGULAR multiplications');
- write ('and divisions by two ':35);
- repeat {Null} until KeyPressed;
- write ('Running':10);
- for J := 1 to 300 do {Coding repeated to reduce the effect}
- begin {of the time required to execute the }
- R := 2 * R; R := R / 2; {the loop. }
- R := 2 * R; R := R / 2;
- R := 2 * R; R := R / 2;
- R := 2 * R; R := R / 2;
- R := 2 * R; R := R / 2;
- R := 2 * R; R := R / 2;
- R := 2 * R; R := R / 2;
- R := 2 * R; R := R / 2;
- R := 2 * R; R := R / 2;
- R := 2 * R; R := R / 2;
- end;
- write (' Finished');
- Data.R := 1.234567;
- writeln; writeln;
- writeln (' Press any key to start 3000 FAST multitplications');
- write ('and divisions by two ':35);
- repeat {Null} until KeyPressed;
- write ('Running':10);
- for J := 1 to 300 do
- begin
- Data.E := succ (Data.E); Data.E := pred (Data.E);
- Data.E := succ (Data.E); Data.E := pred (Data.E);
- Data.E := succ (Data.E); Data.E := pred (Data.E);
- Data.E := succ (Data.E); Data.E := pred (Data.E);
- Data.E := succ (Data.E); Data.E := pred (Data.E);
- Data.E := succ (Data.E); Data.E := pred (Data.E);
- Data.E := succ (Data.E); Data.E := pred (Data.E);
- Data.E := succ (Data.E); Data.E := pred (Data.E);
- Data.E := succ (Data.E); Data.E := pred (Data.E);
- Data.E := succ (Data.E); Data.E := pred (Data.E);
- end;
- write (' Finished');
-
- end
- else {8087 Version. }
- begin
- writeln ('8087 Version':30);
- writeln;
- Data87.R := 1.1111111111111;
- writeln (Data87.R:30, 'Starting number':30);
- Data87.E := Data87.E + $10; {X 2. }
- writeln (Data87.R:30, 'Fast multiplication by two':30);
- Data87.S := Data87.S or $80; {Change sign. }
- writeln (Data87.R:30, 'Fast change of sign':30);
- Data87.E := Data87.E - $20; {Divided by 4. }
- writeln (Data87.R:30, 'Fast division by four':30);
- R := 1.234567;
- writeln;
- writeln (' Press any key to start 5000 regular multitplications');
- write ('and divisions by two ':35);
- repeat {Null} until KeyPressed;
- Ch := ReadKey;
- write ('Running':10);
- for J := 1 to 500 do
- begin
- R := 2 * R; R := R / 2;
- R := 2 * R; R := R / 2;
- R := 2 * R; R := R / 2;
- R := 2 * R; R := R / 2;
- R := 2 * R; R := R / 2;
- R := 2 * R; R := R / 2;
- R := 2 * R; R := R / 2;
- R := 2 * R; R := R / 2;
- R := 2 * R; R := R / 2;
- R := 2 * R; R := R / 2;
- end;
- write (' Finished');
- Data87.R := 1.234567;
- writeln; writeln;
- writeln (
- ' Press any key to start 50000 FAST multitplications');
- write ('and divisions by two ':35);
- repeat {Null} until KeyPressed;
- write ('Running':10);
- for J := 1 to 5000 do
- begin
- Data87.E := Data87.E + $10; Data87.E := Data87.E - $10;
- Data87.E := Data87.E + $10; Data87.E := Data87.E - $10;
- Data87.E := Data87.E + $10; Data87.E := Data87.E - $10;
- Data87.E := Data87.E + $10; Data87.E := Data87.E - $10;
- Data87.E := Data87.E + $10; Data87.E := Data87.E - $10;
- Data87.E := Data87.E + $10; Data87.E := Data87.E - $10;
- Data87.E := Data87.E + $10; Data87.E := Data87.E - $10;
- Data87.E := Data87.E + $10; Data87.E := Data87.E - $10;
- Data87.E := Data87.E + $10; Data87.E := Data87.E - $10;
- Data87.E := Data87.E + $10; Data87.E := Data87.E - $10;
- end;
- write (' Finished');
- end;
- writeln; writeln;
-
- End; {RealFastDemo}
-
- BEGIN (* of Initialization for Unit RealFast *)
-
-
- RealFastDemo; (* Delete this for actual use of unit *)
-
- END.