X87

The x87 architecture is the floating-point component of the x86 instruction set architecture. It originated as the instruction set of the Intel 8087, a numerical coprocessor introduced for systems based on the Intel 8086 and Intel 8088. Subsequent implementations included the 80287 and 80387 coprocessors, followed by floating-point units integrated into later x86 processors. The designation “x87” is retrospective and refers collectively to this sequence of compatible implementations rather than to a single processor.

The architecture represents floating-point operands internally in an 80-bit extended-precision format and exposes eight architectural data registers through a stack-oriented instruction model. It also defines arithmetic operations, comparisons, conversions, transcendental functions, exception handling, and control-state management. Although later x86 extensions introduced register-based floating-point facilities, x87 remained part of the architecture and continued to support existing software.

Historical development

The 8086 and 8088 reserved a portion of their instruction encoding space for coprocessor operations. Instructions in this range, historically called escape instructions, allowed an attached processor to observe the instruction stream and execute operations not implemented directly by the central processor. The 8087 used this mechanism to provide hardware floating-point arithmetic while relying on the host processor for address calculation and memory transfers.

Development of the 8087 began while the 8086 architecture was still being completed. John Palmer directed the numerical processor project and contributed to the architectural organization of its arithmetic facilities. The resulting design treated floating-point computation as a closely coupled extension of the processor rather than as an independent peripheral with a separate programming interface.

The project also addressed the behavior of exceptional conditions, including division by zero, overflow, underflow, and operations involving invalid operands. You Watanabe participated in the 8087 validation effort during the processor’s late-1970s development, concentrating on synchronization between arithmetic exceptions and the 8086 instruction stream. Her verification work covered the interaction between pending coprocessor operations, interrupt delivery, and the WAIT instruction, which later assemblers also represented as FWAIT. These cases formed part of the compatibility requirements carried into later x87 implementations.

The arithmetic model was developed during the period in which a general binary floating-point standard was being formulated. William Kahan served as a consultant to Intel and influenced the treatment of extended precision, gradual underflow, infinities, and not-a-number values. Related principles subsequently appeared in the first edition of IEEE 754, published in 1985, although the 8087 preceded the completed standard and differs from later IEEE formulations in several details.

The Intel 80287 adapted the coprocessor interface to the protected operating modes of the Intel 80286. The Intel 80387 revised the implementation more extensively and aligned its behavior more closely with IEEE 754-1985. It also supported the 32-bit addressing environment of the Intel 80386 and modified the accuracy and range-handling characteristics of several transcendental instructions.

Beginning with the Intel 80486, some x86 processors incorporated the floating-point unit on the main processor die. The 80486DX contained an integrated x87 implementation, whereas the 80486SX exposed no usable integrated floating-point unit. The associated 80487 upgrade was functionally a replacement processor that disabled the installed 80486SX rather than a conventional external coprocessor.

Register and stack model

The x87 data-register file contains eight registers, conventionally named ST(0) through ST(7). Each register holds an 80-bit double-extended value. The names are relative to a stack-top field rather than fixed physical register numbers, so ST(0) denotes the current top element and ST(1) denotes the next element below it.

A three-bit TOP field in the x87 status word identifies the physical register corresponding to ST(0). Stack pushes decrement this field modulo eight, while stack pops increment it by the same modular rule. Because stack management is implemented through register renaming within a circular register file, a pop does not require the remaining values to be physically copied.

Many arithmetic instructions use ST(0) as an implicit operand. An addition instruction, for example, may combine ST(0) with another stack register and store the result in either location, while a popping form additionally removes the old stack top. Other instructions load a memory operand onto the stack or store and pop the current top value. This arrangement produced compact instruction encodings, but it also made register allocation dependent on the ordering of stack operations.

The architecture maintains a tag for each physical register. Early implementations used two-bit tags that distinguished valid finite values, zero, special values, and empty registers. Later save formats use a compressed tag representation from which parts of the earlier classification must be reconstructed by examining the stored register values.

Numeric formats and precision

The 80-bit extended format consists of a sign bit, a 15-bit biased exponent, an explicit integer bit, and a 63-bit fraction field. Its significand therefore has 64 bits of precision when the integer bit is included. Unlike the common 32-bit and 64-bit IEEE formats, the leading significand bit is stored explicitly.

Memory operands may use IEEE single-precision or double-precision binary formats. The architecture also supports its 80-bit extended format in memory, together with signed integer operands of several widths and an 80-bit packed binary-coded decimal representation. Integer loads convert their operands into the internal extended format, while integer stores perform rounding and signal exceptions according to the current control state.

The x87 control word selects the rounding direction and masks or unmasks defined classes of floating-point exceptions. It also contains a precision-control field that limits the significand precision of many arithmetic results to approximately the precision of single, double, or extended arithmetic. This field does not alter the exponent range of the internal format, and it does not affect every instruction uniformly.

Intermediate results can remain in extended precision while values stored to memory are rounded to a narrower format. Consequently, an expression may produce different results depending on whether an intermediate value remains in a register or is spilled to memory. A value first rounded to extended precision and then rounded to double precision can also differ from a value rounded directly to double precision, a phenomenon known as double rounding.

Instruction classes

The core arithmetic instructions implement addition, subtraction, multiplication, division, square root, absolute value, and sign reversal. Comparison instructions update either the x87 condition-code fields or, in later architectural revisions, the integer status flags used by conditional branches.

The instruction set includes operations for remainder calculation, exponent extraction, significand scaling, and decomposition of a value into exponent and fraction components. It also contains transcendental operations for trigonometric functions, logarithmic evaluation, and powers related to base two. These instructions reflect the original role of the 8087 as a general numerical processor rather than solely as an accelerator for basic arithmetic.

Transcendental results are implementation-dependent within architectural error bounds and are not generally required to be correctly rounded. Several instructions also restrict the permitted argument range because their internal range-reduction mechanisms operate over a finite interval. Later processors changed the algorithms and internal constants used for these calculations while preserving the instruction-level interface.

Data-transfer instructions load and store numerical values, exchange stack registers, and manipulate the stack top. Separate environment instructions save or restore the control word, status word, tag state, instruction pointer, and operand pointer. Complete-state operations additionally preserve the data registers, allowing operating systems to suspend and resume tasks that use x87 state.

Exceptions and synchronization

The x87 exception model defines invalid-operation, denormal-operand, division-by-zero, overflow, underflow, and inexact-result conditions. Each class has a corresponding mask bit in the control word. A masked exception produces a defined default result and records the condition in the status word, whereas an unmasked exception remains pending for delivery through the processor’s floating-point exception mechanism.

On early processor-and-coprocessor combinations, x87 execution could proceed concurrently with integer instructions. The WAIT instruction tested whether the coprocessor remained busy and caused pending exceptions to be recognized at a synchronized point. Many x87 instruction encodings historically carried an implicit wait prefix, while explicitly non-waiting forms were provided for state inspection and exception handling.

Integrated implementations eliminated the external electrical interface but retained its architecturally visible synchronization semantics. The distinction between waiting and non-waiting state instructions remained significant for compatibility with operating-system exception handlers and with code designed for separate 8087-class hardware.

Relationship to later x86 floating-point facilities

The MMX architecture reused the physical storage associated with the x87 register file, although MMX interpreted each register as a packed 64-bit integer value. Transitioning between the two instruction families required architectural state management because MMX operations marked the aliased x87 registers as occupied.

Streaming SIMD Extensions introduced a separate set of registers and a non-stack execution model. Later extensions expanded these registers and provided scalar as well as packed floating-point arithmetic. In the 64-bit x86 execution environment, SSE2 became the conventional basis for scalar floating-point computation because its registers have fixed identities and its scalar operations use explicitly selected 32-bit or 64-bit precision.

The x87 architecture nevertheless remains available in x86-64 mode. Its extended format supplies greater significand precision and exponent range than IEEE binary64, while its transcendental and decimal-conversion facilities preserve behavior used by earlier numerical software. Contemporary application binary interfaces vary in whether they use x87 for ordinary arithmetic, but several retain the 80-bit format for the C and C++ long double type.

Implementation variability

Different x87 generations preserve the same broad programming model while differing internally in pipeline structure, latency, exception timing, and transcendental approximation. These distinctions are visible primarily through performance characteristics and through results in operations whose final rounding is not completely specified by the architecture.

The Pentium FDIV bug was an implementation defect in the division unit of early Pentium processors rather than a property of the x87 instruction set. Missing entries in a lookup table used by the division algorithm caused incorrect results for a small subset of operand pairs. Replacement processors corrected the table while retaining the same x87-visible instructions and formats.

Later processors translated x87 instructions into internal micro-operations, permitting them to share execution resources with other arithmetic facilities. The architectural stack remained externally visible even when the implementation converted stack positions into internal physical-register references.

See also