Using the 80x87

There are two kinds of numbers you can work with in Object Pascal: integers (Shortint, Smallint, Longint, Byte, Word, Integer, Cardinal) and reals (Real, Single, Double, Extended, Comp). Reals are also known as floating-point numbers. The 80x86 family of processors is designed to handle integer values easily, but handling reals is considerably more difficult. To improve floating-point performance, the 80x86 family of processors has a corresponding family of math coprocessors, the 80x87s.

The 80x87 is a special hardware numeric processor that can be installed in your PC. It executes floating-point instructions very quickly, so if you use floating point often, you'll probably want a numeric coprocessor or a 486DX or Pentium processor, which has a numeric coprocessor built in.

By default, Delphi produces code that uses the 80x87 numeric coprocessor. This gives you access to all five real types (Real, Single, Double, Extended, and Comp), and performs all floating-point operations using the full Extended range of 3.4 × 10-4951 to

1.1 × 104932 with 19 to 20 significant digits.

For compatibility with earlier versions of Object Pascal, Delphi provides a $N compiler switch which allows you to control floating-point code generation. The default state is {$N+}, and in this state Delphi produces code that uses the 80x87 numeric coprocessor. In the {$N–} state, Delphi supports only the Real type, and uses a library of software routines to handle floating-point operations. The Real type provides a range of 2.9 × 10-39 to 1.7 × 1038 with 11 to 12 significant digits.

Note The Delphi Visual Class Library requires that you compile your applications in the

{$N+} state. Unless you are compiling an application that doesn't use VCL, you should refrain from using the {$N–} state.

To interface with the 80x87 coprocessor, Delphi applications use the WIN87EM.DLL support library that comes with Windows. If an 80x87 coprocessor isn't present in your system, WIN87EM.DLL will emulate it in software. Emulation is substantially slower than the real 80x87 coprocessor, but it does guarantee that an application using the 80x87 can be run on any machine.

The 80x87 data types

Delphi fully supports the the single, double, and extended precision native floating- point formats provided by the 80x87 coprocessor. In addition, Delphi supports the 80x87's 64-bit integer format.

  • The Single type is the smallest format you can use with

    floating-point numbers. It occupies 4 bytes of memory, providing a range of 1.5 × 10-45 to 3.4 × 1038 with 7 to 8 significant digits.

  • The Double type occupies 8 bytes of memory, providing a range of

    5.0 × 10-324 to

1.7 × 10308 with 15 to 16 significant digits.

  • The Extended type is the largest floating-point type supported by

    the 80x87. It occupies 10 bytes of memory, providing a range of 3.4 × 10-4932 to 1.1 × 104932 with 19 to 20 significant digits. Any arithmetic involving real-type values is performed with the range and precision of the Extended type.

  • The Comp type stores integral values in 8 bytes, providing a range

    of -263 +1 to 263 - 1, which is approximately -9.2 × 1018 to 9.2 × 1018. Comp may be compared to a double-precision Longint, but it's considered a real type because all arithmetic done with Comp uses the 80x87 coprocessor. Comp is well suited for representing monetary values as integral values of cents or mils (thousandths) in business applications.

For backward compatibility, Delphi also provides the Real type, which occupies 6 bytes of memory, providing a range of 2.9 x 10-39 to 1.7 x 1038 with 11 to 12 significant digits.

Note that 80x87 floating-point operations on variables of type Real are slightly slower than on other types. Because the 80x87 can't directly process the Real format, calls must be made to library routines to convert Real values to Extended before operating on them. If you're concerned with optimum speed you should use the Single, Double, Extended, and Comp types exclusively.

Extended range arithmetic

The Extended type is the basis of all floating-point computations with the 80x87. Delphi uses the Extended format to store all non-integer numeric constants and evaluates all non-integer numeric expressions using extended precision. The entire right side of the following assignment, for example, is computed in Extended before being converted to the type on the left side:

var

X, A, B, C: Double;

begin

X := (B + Sqrt(B * B - A * C)) / A;

end;

Delphi automatically performs computations using the precision and range of the Extended type. The added precision means smaller round-off errors, and the additional range means overflow and underflow are less common.

You can go beyond Delphi's automatic Extended capabilities. For example, you can declare variables used for intermediate results to be of type Extended. The following example computes a sum of products:

var

Sum: Single;

X, Y: array [1..100] of Single; I: Integer;

T: Extended; { For intermediate results }

begin

T := 0.0;

for I := 1 to 100 do begin

X[I] := I;

Y[I[ := I;

T := T + X[I] * Y[I];

end;

Sum := T;

end;

Had T been declared Single, the assignment to T would have caused a round-off error at the limit of single precision at each loop entry. But because T is Extended, all round-off errors are at the limit of extended precision, except for the one resulting from the assignment of T to Sum. Fewer round-off errors mean more accurate results.

You can also declare formal value parameters and function results to be of type Extended. This avoids unnecessary conversions between numeric types, which can result in loss of accuracy. For example,

function Area(Radius: Extended): Extended;

begin

Area := Pi * Radius * Radius;

end;

Comparing reals

Because real-type values are approximations, the results of comparing values of different real types aren't always as expected. For example, if X is a variable of type Single and Y is a variable of type Double, then these statements are False:

X := 1 / 3;

Y := 1 / 3;

Writeln(X = Y);

This is because X is accurate only to 7 to 8 digits, where Y is accurate to 15 to 16 digits, and when both are converted to Extended, they will differ after 7 to 8 digits. Similarly, these statements,

X := 1 / 3;

Writeln(X = 1 / 3);

are False, because the result of 1/3 in the Writeln statement is calculated with 20 significant digits.

The 80x87 evaluation stack

The 80x87 coprocessor has an internal evaluation stack that can be as deep as eight levels. Accessing a value on the 80x87 stack is much faster than accessing a variable in memory. To achieve the best possible performance, Delphi uses the 80x87's stack for storing temporary results.

In theory, very complicated real-type expressions can overflow the 80x87 stack, but this isn't likely to occur because the expression would need to generate more than eight temporary results.

Detecting the 80x87

The Windows environment and the WIN87EM.DLL emulator library automatically detect the presence of an 80x87 chip. If an 80x87 is available in your system, it's used. If not, WIN87EM.DLL emulates it in software. You can use the GetWinFlags function (defined in the WinProcs unit) and the wf_80x87 bit mask (defined in the WinTypes unit) to determine whether an 80x87 processor is present in your system. For example,

if GetWinFlags and wf_80x87 <> 0 then

WriteLn('80x87 is present')

else WriteLn('80x87 is not present');

Emulation in assembly language

When linking in object files using {$L filename} directives, make sure that these object files were compiled with the 80x87 emulation enabled. For example, if you're using 80x87 instructions in assembly language external procedures, enable emulation when you assemble the .ASM files into .OBJ files. Otherwise, the 80x87 instructions can't be emulated on machines without an 80x87. Use Turbo Assembler's /E command-line switch to enable emulation.

Exception statements

The exceptions statements are the raise statement, the try...except statement, and the try...finally statement. These statements are described in Chapter 10, Exceptions.

C h a p t e r