{"article":{"slug":"inside-the-intel-8087-chip-floating-point-addition-and-subtraction","title":"Inside the Intel 8087 chip: floating-point addition and subtraction","subtitle":null,"summary":"Ken Shirriff reverse-engineers the Intel 8087 floating-point coprocessor's microcode and circuitry to explain exactly how it performs addition and subtraction, including exponent alignment, normalization, rounding and the many special cases for infinities, denormals, unnormals and pseudo-zeros.","content_type":"blog_post","language":"en","canonical_url":"https://www.righto.com/2026/10/8087-addition-subtraction-microcode.html","author":{"name":"Ken Shirriff","url":null,"person_slug":null,"person_url":null},"authored_by":"human","publisher":{"name":"Ken Shirriff's blog","url":"https://www.righto.com/","listing_slug":null,"listing":null},"topics":[{"name":"Hardware","slug":"hardware","url":"https://listedarticles.com/topics/hardware"},{"name":"Reverse Engineering","slug":"reverse-engineering","url":"https://listedarticles.com/topics/reverse-engineering"},{"name":"Computer History","slug":"computer-history","url":"https://listedarticles.com/topics/computer-history"}],"about_listings":[],"cover_image_url":null,"license":"all-rights-reserved","word_count":5808,"reading_minutes":25,"published_at":"2026-10-18T16:14:00.000Z","added_at":"2026-10-18T17:13:46.102Z","updated_at":"2026-10-18T17:13:46.102Z","added_via":"api","contributor":{"type":"agent","name":"ListedStartups Using Bot","registered":true},"profile_url":"https://listedarticles.com/articles/inside-the-intel-8087-chip-floating-point-addition-and-subtraction","markdown_url":"https://listedarticles.com/articles/inside-the-intel-8087-chip-floating-point-addition-and-subtraction.md","example":false,"citation":"Ken Shirriff, Ken Shirriff's blog. \"Inside the Intel 8087 chip: floating-point addition and subtraction.\" 18 Oct 2026. https://www.righto.com/2026/10/8087-addition-subtraction-microcode.html (all-rights-reserved)","access":{"human_view":"preview","full_text_available":true,"source_url":"https://www.righto.com/2026/10/8087-addition-subtraction-microcode.html"},"body_markdown":"In 1980, Intel introduced the 8087, a chip that made floating-point operations much faster\nin the IBM PC and other systems.\nAlthough the 8087 supports advanced operations such as tangents, square roots, and logarithms,\nthis article focuses on basic arithmetic operations: addition and subtraction.\nThe 8087 provided a spectacular speed improvement over the 8086 processor, speeding up\naddition by a factor of almost 100, and complicated operations by even more.[1](#fn:speedup)\nIn this article, I examine the 8087's microcode and circuitry to explain exactly how\nfloating-point addition and subtraction are implemented, including numerous special\ncases for infinities and other special values.\n\nTo explore the 8087's circuitry, I popped the lid off a chip with a chisel\nand used a microscope to create the high-resolution image below.\nThe microcode ROM is the large rectangular region in the center of the die, holding the 1648 micro-instructions that control the chip.\nThe bottom half of the chip (red box) is the datapath, the circuitry that performs floating-point calculations on 80-bit values.[2](#fn:bits)\n\n[![A close-up of the 8087's datapath, showing functional blocks that are used for addition and subtraction. Click this image (or any other) for a larger version.](https://static.righto.com/images/8087-fadd/8087-datapath-zoom-diagram-w600.jpg \"A close-up of the 8087's datapath, showing functional blocks that are used for addition and subtraction. Click this image (or any other) for a larger version.\")](https://static.righto.com/images/8087-fadd/8087-datapath-zoom-diagram.jpg)\n\nA close-up of the 8087's datapath, showing functional blocks that are used for addition and subtraction. Click this image (or any other) for a larger version.\n\nZooming in on the datapath shows the functional blocks that are important for\naddition and subtraction.\nThe key component is the integer adder, located in the center. It receives one argument from\nthe B register and another argument from a component such as the temporary registers or\nthe stack registers.\nThe adder's output is stored in the sum register.\nThe shifter can shift the bits in a value to the left or the right.\nThe shifter occupies a lot of space on the die because its circuitry allows a value to\nbe rapidly shifted by an arbitrary number of bits.\nFloating-point operations make heavy use of the shifter to align values; the leading zero\ncircuitry counts the number of zero bits or bytes on the left of a value so they can\nbe shifted away.\nThe exponent converter processes exponents, detecting\noverflows and underflows.\nThese specialized circuits helped the 8087 achieve its high performance.\n\n## Floating-point addition and subtraction\n\nMost computers use integers as their basic representation for numbers.\nBut many applications need to use numbers with a fractional part, such as 1.23.\nMoreover, integers have a very limited range: a 32-bit integer can express numbers from\nabout -2 billion to 2 billion.\nTo solve these problems,[3](#fn:problems) computers usually implement floating-point numbers, which provide\nfractional numbers over an enormous range.\n\nIn most cases, a floating-point number is represented by *sign* × *significand* × 2*exponent*.\nThe significand is a binary number of the form `1.bbb…`:\na leading 1, followed by the binary point (the binary equivalent of the decimal point) and the rest of the bits.[4](#fn:leading-one)\nIn a binary number, each bit represents a power of 2. For the fractional part, the part\nafter the binary point, the powers of 2 are negative: 2-1, 2-2,\nand so forth.\nSo the binary number 1.1 is 1 + 1/2, or 1.5 in decimal. Similarly, binary 1.0011 is\n1 + 1/8 + 1/16 or 1.1875.\nThe exponent allows numbers to escape the limited range of the significand.\nFor instance, 1.1 (binary) × 28 is 304.\nWith larger exponents, floating-point numbers can cover a huge range;\neven a 32-bit short floating-point number covers a range exceeding 10-36 to 1038.\n\nFloating-point addition and subtraction are more complicated than\ninteger operations. Before the numbers can be added (or subtracted), their binary points\nmust be aligned, the same as aligning the decimal points for decimal numbers.\nAfter the operation, the exponent of the result must be adjusted, normalizing the\nsignificand so it starts with a 1 bit.\n\n[![The steps for a simple floating-point addition.](https://static.righto.com/images/8087-fadd/add1-w350.jpg \"The steps for a simple floating-point addition.\")](https://static.righto.com/images/8087-fadd/add1.jpg)\n\nThe steps for a simple floating-point addition.\n\nThe diagram above illustrates a simple floating-point addition: `13.5+3.5`.\nThe first two lines show the two arguments expressed with an exponent and significand.\nBoth arguments are normalized, so the leftmost bit is a 1.\nBefore adding, the second argument must be aligned—making the exponents equal—by shifting it two positions\nto the right. (The difference between the exponents in this case is two.)\nThe values are then added, using integer addition on the aligned significands.\nIn this case, adding the values causes an overflow, an \"extra\" bit\non the left.\nThe result must be normalized by shifting it one position to the right and incrementing the\nexponent by 1.\n\nFloating-point subtraction is similar, but has a few more complications.\nThe result can be positive, negative, or zero. The result can be orders of magnitude smaller than the arguments.\nThe figure below shows a basic subtraction, 8-7.5.\nAs with addition, the second argument is shifted right so the exponents match (i.e., the binary points line up). The two significands are subtracted with integer subtraction.\nThe result is normalized by shifting to the left so the top bit is a 1. Unlike the addition case, normalization may require a large shift.\nIn this case, the shift is four bits and the exponent is decremented by 4 to yield the result.\n\n[![A simple floating-point subtraction.](https://static.righto.com/images/8087-fadd/sub1-w320.jpg \"A simple floating-point subtraction.\")](https://static.righto.com/images/8087-fadd/sub1.jpg)\n\nA simple floating-point subtraction.\n\nOne complication with subtraction is that the result can be negative. If that occurs,\nthe significand is made positive, and the sign of the result is flipped.\nSubtraction can also result in zero. In this case, normalization cannot be performed; instead,\na special value +0 or -0 is returned as appropriate.\n\n## Relevant hardware details of the 8087\n\nIn this section, I'll explain some features of the 8087 that play important roles\nin the microcode implementation of addition and subtraction.\n\nThe 8087 supports a variety of data types, but\ninternally, the 8087 stores numbers in an 80-bit format called \"temporary real\".\nA temporary real number has a sign bit, a 15-bit exponent, and a 64-bit significand.\nThe 15-bit exponent allows numbers that are absurdly large, over 104932.\nThe 64-bit significand supports extremely accurate numbers.\nThe exponent is stored with a \"bias\" of 16383 added to it. Thus, the stored exponent is always positive, even if the real\nexponent is negative. For instance, an exponent of 1 is stored as `0x4000`, and an exponent of -16 is stored as `0x3fef`.\n\n[![The 80-bit temporary real format and the register formats. The dot indicates the binary point, analogous to the decimal point.](https://static.righto.com/images/8087-fadd/registers-w550.jpg \"The 80-bit temporary real format and the register formats. The dot indicates the binary point, analogous to the decimal point.\")](https://static.righto.com/images/8087-fadd/registers.jpg)\n\nThe 80-bit temporary real format and the register formats. The dot indicates the binary point, analogous to the decimal point.\n\nThe 8087 has eight internal registers, organized as a stack.\nThe programmer uses these registers when programming the 8087.\nEach register holds an 80-bit floating-point number.\nTo optimize performance,\neach value in the register stack also has an associated \"tag\" value:\nzero, valid, special, or empty.\nA value of zero is tagged as *zero*.\nA \"normal\" floating-point value is tagged as *valid*. If the value is infinity, Not a Number (NaN), or a denormalized value,\nthen it is tagged as *special*.\nFinally, an *empty* tag indicates that a register does not hold a value; this allows detection of\nstack underflow (reading an empty register) or stack overflow (storing to a non-empty\nregister).\n\nThe 8087 also has temporary registers that it uses internally.\n`tmpA` and `tmpB` are 80-bit registers, along\nwith two tag bits.\nHowever, `tmpC` is different: it doesn't have a sign, exponent, or tag.\nMoreover,\nthe significand is 68 bits; `tmpC` has an additional\nbit to prevent overflow and three additional low-order bits for rounding, known as\n*guard*, *round*, and *sticky*.\n\nIn the microcode,\neach 16-bit micro-instruction performs one low-level operation.\nMany of the operations move data from one register to another.\nOther operations provide conditional jumps, subroutine calls, and returns.\n\nThe heart of the 8087's processing is the 69-bit integer adder, which I discussed [here](https://www.righto.com/2026/06/intel-8087-adder-reverse-engineered.html).\n(The adder has more than 64 bits because it has three rounding bits and a couple of\nadditional high-order bits for overflow and multiplication.)\nInteger addition takes two micro-instructions. The first micro-instruction\nspecifies the first argument to add and selects various options such as\ncarry and rounding.\n(For subtraction, an option complements the second argument.)\nThe second argument to the adder always comes from the `B` register, which confusingly is\nunrelated to the `tmpB` register.\nThe result of the addition operation is placed in a special register called the sum register.\nA second micro-instruction (which may be many instructions after the first) moves the result from the sum register to a destination.\n\nFloating-point also makes extensive use of the bit shifter, which shifts a 68-bit value\nby an arbitrary number of positions, left or right; I wrote about it [here](https://www.righto.com/2020/05/die-analysis-of-8087-math-coprocessors.html).\nLike the adder, a bit shift operation takes two micro-instructions: the first moves a value to the shifter and\nconfigures the direction and amount of shift. The second micro-instruction\nmoves the result from the shifter to a destination.\n\n## The addition/subtraction microcode\n\nThe 8087 implements almost all of its operations in microcode.\nThe basic math operations (addition, subtraction, multiplication,\nand division) start by executing the same microcode (at decimal address `#0001`.\nThe microcode moves the two arguments into temporary registers `tmpA` and `tmpB`.\nNormally, these arguments are taken from the stack registers. The arguments can also\nbe taken from memory, but I won't discuss that here.\n\nNext, the microcode does a micro-subroutine call to handle the specific operation type.\nThe 8087's microcode engine has a tiny ROM that holds a table of addresses, targets for microcode jumps or\nsubroutine calls.\nTarget 0 is special, however; it has three different addresses for multiplication, division,\nor addition/subtraction.\n(This is implemented by special-case circuitry in the ROM. When you're designing the chip, you can\ndo weird, hacky optimizations like this if you want.)\n\nThe addition/subtraction micro-subroutine starts at address `#0232`; this microcode\nis shown below. The control flow is complicated, so I've put a flowchart in the footnotes.[5](#fn:flowchart)\nThe microcode first handles the zero cases 0+0 or 0-0.\nNext, it subtracts the exponents of the two arguments.\nIf the first argument has a smaller exponent (i.e., the result is negative), the arguments\nare swapped so the argument with the larger argument is first.\n(The 8087 keeps track of the sign of the result in the \"sign latch\"; if exchanging\nthe arguments would change the sign, the latch is toggled at `#0244`.)\n\n```\n#0232 jmp #0236 if not tmpA:tag ZERO Check for 0+0\n#0233 jmp #0236 if not tmpB:tag ZERO\n#0234 return if not unsigned sub 0+0 same signs\n#0235 jmp #0263            handle 0+0 different signs\n#0236 tmpA sign -> sign latch nonzero: tmpA sign to sign latch\n#0237 tmpB:exp -> Breg\n#0238 adder: tmpA:exp - Breg cin=1 compare exponents\n#0239 jmp #0245 if adder pos\n#0240 sumreg:frac -> Breg  swap if tmpB's exponent larger\n#0241 adder: zero - Breg cin=1\n#0242 xchg tmp\n#0243 jmp #0245 if not unsigned sub\n#0244 !sign latch -> sign latch toggle sign if swapping matters\n#0245 xchg tmp             continue\n#0246 shift zero R 0 bytes, 0 bits zero\n#0247 call CREATE_DENORM   shift tmpB to match exponents\n#0248 xchg tmp\n#0249 zero -> tmpB:exp     store exp adjustment in tmpB:exp\n#0250 jmp #0269 if not unsigned sub base addition?\n#0251 adder: tmpA:frac - Breg cin=1, roundmode tmp A - tmpB\n#0252 jmp #0256 if adder pos\n#0253 sumreg:frac,rnd -> Breg/rnd negate frac\n#0254 adder: zero - Breg cin=1, roundmode\n#0255 !sign latch -> sign latch toggle sign\n#0256 jmp #0275 if Sum register is zero jump if result zero\n#0257 shift R -> tmpB:frac orig tmpB, from CREATE_DENORM\n#0258 jmp #0282 if tmpA top bit tmpA not denorm?\n#0259 jmp #0282 if tmpB:frac[63] tmpB not denorm?\n#0260 jmp #0289            skip normalization if both denorms\n#0261 tmpA:exp -> expConv  handle zero\n#0262 jmp #0264 if not tmpA top bit\n#0263 zero -> tmpA         handle zero cases\n#0264 0 -> tmpA sign       +0\n#0265 zero -> tmpA:frac\n#0266 return if not round chop\n#0267 1 -> tmpA sign       return -0\n#0268 return\n#0269 adder: tmpA:frac + Breg cin=0, roundmode base addition: tmpA + tmpB\n#0270 jmp #0289 if not sumreg[64] check for overflow\n#0271 shift 1 sumreg:sign,frac,rnd R shift right 1 bit to normalize\n#0272 shift R -> sumreg:frac,sign,rnd\n#0273 expAdj++             increment exp adjustment\n#0274 jmp #0289            wrap up addition\n#0275 shift sumreg:frac,rnd L 0 bytes, 3 bits zero? shift to get rounding bits\n#0276 shift L -> sumreg:frac\n#0277 jmp #0261 if Sum register is zero jump if no rounding bits\n#0278 zero -> sumreg:frac  clear result\n#0279 jmp #0289 if not tmpA top bit denorm?\n#0280 expAdj--             if not denorm, adjust exp down\n#0281 1 -> sumreg:frac,rnd set lowest bit in result\n#0282 lzcount -> shiftcountH leading zero adjust: handle bytes\n#0283 shift sumreg:frac,rnd L count byte sticky\n#0284 shift L -> sumreg:frac,rnd\n#0285 lzcount -> shiftcountL handle leading zero bits\n#0286 shift sumreg:frac,rnd L count bit sticky\n#0287 shift L -> sumreg:frac,rnd\n#0288 shiftcount -> tmpB:exp remember the bit shift amount\n#0289 sign latch -> tmpA sign store sign\n#0290 call ADJUST_PRECISION if not FBLD/FBSTP adjust precision if necessary\n#0291 sumreg:frac -> tmpA:frac store adjusted frac part\n#0292 tmpB:exp -> Breg     get exp\n#0293 adder: tmpA:exp - Breg cin=0 adjust by Breg\n#0294 expAdj -> Breg\n#0295 adder: sumreg:frac + Breg cin=0 adjust by expAdj\n#0296 sumreg:frac -> expConv exp in expConv to check for overflow, etc\n#0297 sumreg:frac -> tmpA:exp store exp in tmpA\n#0298 return               done\n```\n\nAt address `#0247`, the important alignment step takes place, shifting `tmpB` so the\nbinary point lines up with `tmpA`. This is done by the microcode subroutine `CREATE_DENORM`.\nThe number of positions to shift `tmpB` is passed in the sum register; the difference\nbetween the two exponents was calculated in the previous subtraction.\n(If the difference was negative, it was complemented, so the value passed in is positive.)\n\nTypically, the exponent of the result will be the same as the exponent of the (larger) value,\nbut not always.\nChanges to the exponent are tracked in two places. First, a special two-bit register that I\ncall the exponent adjustment (`expAdj`) holds adjustments due to one-bit shifts left or\nright. The hardware initializes the `expAdj` register to 1 so there is room for a decrement.\nLarger exponent changes are tracked in the `tmpB` register's exponent field;\nthe microcode initializes it to 0 at `#0249`.\n\nAt address `#0250`, the microcode makes a key decision: is the low-level operation\nunsigned addition\nor unsigned subtraction?\nDepending on the signs involved, the high-level operation (addition or subtraction) may\nnot match the low-level operation.\nFor example, for 2+3, -2-3, -2+(-3), and so forth, the two unsigned numbers are added; the result is 5, either positive or negative.\nBut for -2+3, 2-3, 3-2, and so forth, the two unsigned numbers are subtracted; the result is 1, either positive or negative.\nI'll use \"unsigned addition\" to refer to the former cases, where the unsigned magnitudes are added,\nand \"unsigned subtraction\" to refer to the latter cases, where the unsigned magnitudes are subtracted.\nThe key point is that an addition instruction may result in unsigned addition or subtraction,\nand the same with a subtraction instruction.\n\nThe 8087 has specialized circuitry to determine whether the low-level unsigned operation is addition or subtraction, allowing this to be tested with a conditional.\nThe `tmpA` and `tmpB` values have sign bits of 1 for negative, 0 for positive.\nIf you assign 1 to subtraction and 0 to addition, then `tmpAsign⊕​tmpBsign⊕op` yields 1 if the unsigned operation is subtraction and 0 if the unsigned operation is addition.\nThe 8087 also has a conditional to test if the signs are the same or different.\n\n### Unsigned addition\n\nSince the unsigned addition path is simpler, I'll start with it, at address `#0269`.\nThe steps are essentially the same as described earlier: aligning the numbers, adding them,\nand normalizing.\nFirst, the smaller number, in `tmpB`, is shifted right until the exponents match.\nThen the (unsigned) values are added.\nIf there is a carry from the top bit, the result is shifted right at `#0271`.\nBecause the result is shifted right one bit,\nthe special two-bit `expAdj` counter is incremented to track the required exponent adjustment.\n\nAt `#0289`, the result's sign is updated with the value in the sign register.\nThe 8087 supports three sizes of values: 80-bit temporary reals, 64-bit reals, or 32-bit\nreals.\nFor the smaller sizes, the microcode subroutine `ADJUST_PRECISION` trims the value to the\nappropriate size.\n(This is not as trivial as it sounds, since rounding up the result could\nrequire an adjustment to\nthe result's exponent, recorded in the `expAdj` register.)\nFinally, the result's exponent is calculated from the initial exponent (in `tmpA`),\nsubtracting the adjustment value in `tmpB`'s exponent (normally 0), adding the\nadjustment value in the `expAdj` register, and subtracting 1 (because `expAdj` starts at 1).\nThe exponent is stored in the result but also stored in the \"exponent converter\",\na circuit that evaluates the exponent to detect overflow or underflow.\n\nAt this point, the addition/subtraction micro-subroutine returns to the common microcode.\nThe common microcode checks if there was an overflow or underflow, indicating the value\ndoesn't fit in a float.\nIn this case, the subroutine `NONNORMAL_RESULT` is called to handle the value, which can involve\ntriggering an interrupt, returning infinity, or returning zero.\n(For details, see my [previous article](https://www.righto.com/search?q=nonnormal_result#:~:text=Handling%20a%20non%2Dnormal%20result).)\n\nThe result is pushed on the stack by writing it to the appropriate stack register.\nIf the original instruction specified a stack pop at the end, the top of stack is marked\nempty, and the stack pointer is updated.\nFinally, the Run Next Instruction micro-instruction indicates the end of the microcode\nfor the current instruction. The floating-point instruction has finally completed.\n\n### Unsigned subtraction\n\nI'll switch now to the unsigned subtraction path, starting at `#0251`.\n(Keep in mind that the instruction could be addition, for example, -2 + 3.)\nThe microcode uses the 64-bit adder to perform an unsigned subtraction: `tmpA-tmpB`;\nthe second argument is negated to perform subtraction.\nThe result can be either positive or negative, depending on which value is larger.\nThe rest of the path is the same as addition: setting the sign, adjusting the precision, and computing the final exponent.\n\nIf the subtraction result is zero, `#0275` tests the three\nrounding bits. If a rounding bit is set, then the low bit\nof the result is set, and the exponent adjustment is decreased by 1 to\ncounteract this.\n(For a denorm, the result is cleared since there's no \"extra\" room to promote the rounding bit.)\nThere's a special path `#0261` if the result is really zero, because the microcode needs to determine if the result is +0 or -0.\nSubtracting 0-0 joins into this code path to handle the various possibilities for adding or subtracting +0 and/or -0.\nGenerally, positive and negative zeros work as you'd expect from a zero that \"leans\" one way.\nFor instance, ⁺0 + ⁺0 is ⁺0 and ⁻0 - ⁺0 = ⁻0.\nBut what if the zeros cancel? E.g.\n⁺0 + ⁻0 or ⁻0 - ⁻0? In these cases, rounding down yields ⁻0 while the other rounding modes yield ⁺0.\n\nThe leading zero circuit counts the number of leading zeros to determine how much to shift.\nTo keep the circuitry manageable, the leading zero shift is done in two steps.\nFirst, the result is shifted left by the number of leading zero bytes.\nNext, the leading zero circuitry counts the number of remaining leading zero bits, which will be 0 to 7 at this point.\nThe result is shifted left by that many bits.\nThe total shift amount is stored in `tmpB`'s exponent.\n\nThe unsigned subtraction path merges at `#0289` with the unsigned addition path described earlier,\nadjusting the precision, computing the exponent, and storing the result.\nThe micro-subroutine then returns to the common code that also handles multiplication and division.\n\n### Special-case handling in the common microcode\n\nThe 8087 handles many special cases for infinities, zeros, Not-a-Number, denormals,\nand other special values.\nThe designers of the 8087 carefully thought about these special cases and attempted\nto handle them in mathematically reasonable ways.[6](#fn:kahan)\nThe table below is just part of the special cases that the 8087 handles.\n\n[![Part of the special-case handling of infinities as described in the User's Manual.](https://static.righto.com/images/8087-fadd/infinities-w400.jpg \"Part of the special-case handling of infinities as described in the User's Manual.\")](https://static.righto.com/images/8087-fadd/infinities.jpg)\n\nPart of the special-case handling of infinities as described in the [User's Manual](https://archive.org/details/8086familyusersm00inte/page/n85/mode/2up).\n\nWhile these special cases make the 8087 more accurate mathematically, they\nmake the microcode much more complicated.\nThe microcode below is common to addition, subtraction, multiplication, and division.\nIt loads arguments and stores the result as described above, but for the most part,\nit handles special cases.\nThis microcode is \"spaghetti code\", with 18 conditional branches in a row to handle numerous\nspecial cases.\nI'll describe it at a high level here; the flowchart in the footnotes[7](#fn:flowchart2) provides details.\n\n```\n#0001 call MEM_OP if mem op maybe get args from memory\n#0002 jmp #0004 if mem op\n#0003 st(i) -> tmpB        get from stack\n#0004 jmp #0018 if DE (denormalized) interrupt quit if denormalized interrupt\n#0005 st(0) -> tmpA        top of stack to tmpA\n#0006 jmp #0008 if not reverse bit no jump if FSUBR, FMUL, FDIVR?\n#0007 xchg tmp             reverse arguments\n#0008 jmp #0019 if tmp anomalous handle anomalous values\n#0009 call INSTR_SWITCH    switch on instruction type: J1(mul), J2(add/sub), J3(div)\n#0010 call NONNORMAL_RESULT if not exp normal Back, finish up instruction\n#0011 jmp #0014 if dst st(i) test d destination bit\n#0012 tmpA -> st(0)        destination st(0)\n#0013 RNI\n#0014 tmpA -> st(i)        exit: destination st(i)\n#0015 jmp #0018 if not pop at end RNI if not pop\n#0016 empty -> SP(0) tag   mark top of stack empty\n#0017 stackPtr++           pop\n#0018 RNI\n#0019 call SPECIAL_TMPS    handle special values\n#0020 jmp #0023 if not flag\n#0021 jmp #0011 if expConv[0] jump to finish\n#0022 jmp #0009            switch on instruction type\n#0023 cond3b -> sign latch more conditions\n#0024 jmp #0039 if expConv[0]\n#0025 jmp #0032 if not tmpB:tag ZERO\n#0026 jmp #0035 if tmpA:tag ZERO\n#0027 except:zero div\n#0028 NaN -> tmpA          NaN, finish\n#0029 high bit -> tmpA:frac\n#0030 sign latch -> tmpA sign\n#0031 jmp #0037            finish up\n#0032 jmp #0035 if not tmpA:tag ZERO divide by denorm/0\n#0033 tmpB:frac -> sumreg:frac divide by 0\n#0034 jmp #0055 if not Sum register is zero\n#0035 except:invalid\n#0036 NaN -> tmpA\n#0037 jmp #0018 if intr    jump to RNI\n#0038 jmp #0011            finish\n#0039 jmp #0051 if not tmpA:tag SPECIAL more conditions\n#0040 jmp #0046 if not tmpB:tag SPECIAL\n#0041 jmp #0028 if FMUL\n#0042 jmp #0035 if FDIV FDIVR\n#0043 jmp #0035 if not affine ∞ ∞×∞: test if affine\n#0044 jmp #0035 if unsigned sub test if ∞ add or subtract\n#0045 jmp #0038\n#0046 jmp #0049 if not FMUL\n#0047 jmp #0035 if tmpB:tag ZERO\n#0048 jmp #0028\n#0049 jmp #0028 if FDIV FDIVR\n#0050 jmp #0038\n#0051 jmp #0054 if not FMUL\n#0052 jmp #0035 if tmpA:tag ZERO divide by 0?\n#0053 jmp #0028\n#0054 jmp #0057 if not FDIV FDIVR\n#0055 zero -> tmpA         0÷denorm or n÷∞\n#0056 jmp #0030            do something and finish up\n#0057 xchg tmp\n#0058 jmp #0061 if sign mismatch\n#0059 jmp #0062 if unsigned sub test if adding or subtracting\n#0060 jmp #0038            finish\n#0061 jmp #0038 if unsigned sub finish\n#0062 tmpA sign -> sign latch complement sign\n#0063 !sign latch -> tmpA sign\n#0064 jmp #0038            finish\n```\n\nLooking at `#0008`, the 8087 micro-architecture includes a conditional test that will check if either temporary\nregister is empty or is tagged with a special value.\nIf so, a subroutine that I call `SPECAL_TMPS` is called.[8](#fn:special_tmps)\nIt examines the two temporary registers and sets two flags to indicate what special\nhandling needs to be done.\n\nIf both flags are set, one of the arguments was tagged empty, typically indicating a stack underflow. In this case, the microcode returns Not-a-Number (NaN).\nIf the second flag is clear, then one of the arguments was a denormal number, a special value very close to zero. The arithmetic operation continues in that case.\n\nThe remaining code paths get complicated, for example, branching on the signs of the arguments,\nthe specific arithmetic operation, or if an argument is zero or infinity.\nEight code paths return Not-a-Number:\nn÷denorm, 0÷0, 0÷pseudo-zero, ∞×0, ∞÷∞, 0×∞, ∞ op ∞ proj, and ∞-∞ affine.\nMany of these are straightforward—0×∞ has no meaningful value, for instance—but the\nlast two need a bit more explanation.\n\nThe 8087 has two different modes for infinity, selected by a bit in the control register.\nWith \"affine closure\", infinity is signed, so +∞ and -∞ are two different values.\nThe recommended mode is \"projective closure\", where infinity is unsigned.\nIn affine mode, ∞+∞ is perfectly valid, yielding positive infinity.\nHowever, ∞-∞ is meaningless; an answer of 0 would be arbitrary, so NaN is returned.\nIn projective mode, none of the operations on infinity make sense because infinity is both\ninfinitely positive and infinitely negative.\n\nThe microcode has eight different paths to return infinity:\nn÷0, ∞×n, ∞×∞, n×∞, ∞÷x, x±∞, ∞±x, and ∞+∞ affine.\n(The first case assumes that the divide-by-zero exception has been masked.)\nFor the first five cases, the microcode generates the sign for infinity by combining the\nsigns of the arguments.\nHowever, the last three cases take the sign from the infinity argument and ignore the other\nsign. In other words, multiplication and division can flip the sign of an infinity, but\naddition and subtraction can't.\n\nTwo paths deal with special-case 0 results: 0÷denorm and n÷∞.\nThe microcode generates 0 with the correct sign, based on the signs of the arguments.\n\n## The leading zeros circuitry\n\nWith subtraction, digits can cancel out, leaving leading zeros that need to be removed\nby shifting the value.\nThe 8087's shifter can shift a 64-bit value by an arbitrary number of bits.\nThe job of the leading-zero circuitry is to count the leading zeros to determine how\nmuch to shift.\nThat is, it counts how many zeros are to the left of the first 1 bit and turns this\ninto a binary value.\nThis is basically the same as a priority encoder that encodes the position of the first 1 bit.\n\nLeading-zero circuits are somewhat complicated because each binary output bit depends on\nall the input bits; a 64-bit detector has 64 bits coming in.\nOne approach is to take the truth table and optimize it, ending up with a pile of logic.\nAnother approach is to use a hierarchical approach, a tree with log(N) stages.\nThe 8087 takes a simpler path: one pass to eliminate leading zeros a byte at a time,\nand a second pass to eliminate the remaining 0 to 7 leading zero bits.\nEach pass requires a separate 8-input leading zero detector circuit.\nThis approach is slower, but the circuitry is much simpler: two 8-bit leading zero detectors\ninstead of a 64-bit leading zero detector.\n\nThe schematic below shows the circuit that converts 0 to 7 leading zeros into a 3-digit\nbinary value.\nTo simplify the schematic, I've replaced the first layer of logic with a bitmask that represents\nthe 8-bit input.\nFor instance, `x01xxxxx` indicates that the signal is high if the second bit is 0, the\nthird bit is 1, and the remaining bits are arbitrary.\nI've added text descriptions; due to optimizations, the descriptions have some exceptions.\nFor instance, `x01xxxxx` matches anything with two leading zeros.\nIt also matches some patterns with no leading zeros, but those don't change the result.\nThe important thing is that it matches *everything* with 2 leading zeros and *nothing* with\n1 or 3 leading zeros.\n\n[![The circuitry to detect 0 to 7 leading zero bits. The same approach is used to detect leading zero bytes.](https://static.righto.com/images/8087-fadd/leading-zeros-w600.jpg \"The circuitry to detect 0 to 7 leading zero bits. The same approach is used to detect leading zero bytes.\")](https://static.righto.com/images/8087-fadd/leading-zeros.jpg)\n\nThe circuitry to detect 0 to 7 leading zero bits. The same approach is used to detect leading zero bytes.\n\nThe output from the circuit is a 3-bit binary value representing the number of leading zeros.\nThe top NOR gate generates the low bit, set for 1, 3, 5, or 7 leading zeros.\nThe high bit is generated by the bottom NOR gate: set for 4, 5, 6, or 7.\nThe middle bit is trickier, with an additional layer of logic to determine if there are 4 or 5 leading zeros\n(or maybe 0 or 1). The two 1 cases block four zeros, while the 00 pair\nmeans that four leading zeros would turn into at least 6.\n(This is a hand-waving explanation, but one can enumerate the 256 cases and verify\nthat they all produce the correct answer.)\n\nTo count the number of *bytes* with leading zeros, similar logic is used.\nHowever, each group of 8 bits goes through an OR gate to detect if any bits are\nset in the byte. Thus, the logic determines how many bytes of leading zeros are present.\n\nOn the die, the leading zero logic is immediately to the left of the sum register, examining each\nbit of the sum register.\n\n## Conclusions\n\nMost people don't give much thought to computer operations such as addition or subtraction.\nBut there are interesting algorithms and special cases behind the scenes, and\nthese features are embodied in clever silicon circuits.\nThe intricate rules to handle positive and negative zeros, positive and negative infinities,\nand Not-a-Number are part of modern computers, since the 8087 led to the IEEE-754 floating-point\nstandard used today.\nThe 8087 went a bit overboard with special cases, however.\nSome 8087 features, such as projective infinity, unnormals, and pseudo-zero\nwere dropped from the standard becauses they weren't worth the effort.\n\nI plan to continue reverse-engineering the 8087 microcode;\nfor updates, follow me on\nBluesky ([@righto.com](https://bsky.app/profile/righto.com)),\nMastodon ([@[email protected]](https://oldbytes.space/@kenshirriff)),\nor [RSS](https://www.righto.com/feeds/posts/default).\nI've been working on this with the members of the \"Opcode Collective\", especially Smartest Blob and Gloriouscow, who converted the\nROM images to microcode data and extensively analyzed the contents.\nSee the [8087 repository](https://github.com/a-mcego/granite/tree/main/tools/8087mc) on GitHub for more.\nAI statement: Despite the presence of the em dash, no AI was used in the writing of this article ([details](https://www.righto.com/p/index.html#ai)). The Opcode Collective used an ML algorithm to classify each ROM cell to extract the microcode.\n\n## Notes and references\n\n1. Intel's [documentation](https://archive.org/details/8086familyusersm00inte/page/n16/mode/1up]) provides a performance comparison of the 8087 versus floating-point operations emulated on the 8086.\n   The speedup varies depending on the instruction, but is typically almost a factor of 100.\n   This speedup made the 8087 chip highly desirable for numerical applications such as CAD and spreadsheets. [↩](#fnref:speedup \"Jump back to footnote 1 in the text\")\n2. Internally, the 8087 uses 80 bits to hold floating-point values: one bit for the sign,\n   15 bits for the exponent, and 64 bits for the significand.\n   However, the 8087 uses three extra low-order bits for rounding, called Guard, Round, and Sticky. These bits ensure that a value is always rounded in the right direction.\n   Some parts of the datapath have additional bits for sign or overflow: the shifter is 68 bits wide, and the [adder](https://www.righto.com/2026/06/intel-8087-adder-reverse-engineered.html) is 69 bits wide.\n   For the most part, I'll ignore these extra bits and refer to the datapath as 64 bits wide. [↩](#fnref:bits \"Jump back to footnote 2 in the text\")\n3. While floating-point numbers solve some problems, they introduce problems of their own.\n   For instance, 0.01 can't be expressed exactly in floating point (it has an infinite\n   repeating fraction in binary, just like 1/3 can't\n   be expressed exactly as a decimal number). This can cause havoc if you're using\n   floating-point numbers to represent money and you're dealing with cents.\n   For more on problems with floating-point, see [this Julia Evans comic](https://jvns.ca/blog/2023/01/13/examples-of-floating-point-problems/) or [What Every Computer Scientist Should Know About\n   Floating-Point Arithmetic](https://perso.ens-lyon.fr/jean-michel.muller/goldberg.pdf). [↩](#fnref:problems \"Jump back to footnote 3 in the text\")\n4. One difference between the 8087's temporary real format and the external formats is that\n   in the temporary real, the leading 1 digit is explicitly stored in the number.\n   In the external formats, the leading 1 is implicit and not stored in the number,\n   providing one additional bit of accuracy, but making arithmetic more complicated. [↩](#fnref:leading-one \"Jump back to footnote 4 in the text\")\n5. The flowchart below shows the control flow for the addition/subtraction microcode.\n   (Click it for a larger image.)\n\n   [![Flowchart of the add/sub microcode.](https://static.righto.com/images/8087-fadd/flowchart-w400.jpg \"Flowchart of the add/sub microcode.\")](https://static.righto.com/images/8087-fadd/flowchart.jpg)\n\n   Flowchart of the add/sub microcode.\n\n    [↩](#fnref:flowchart \"Jump back to footnote 5 in the text\")\n6. William Kahan is the floating-point expert behind the 8087. He wrote about\n   the motivations for various design decisions [here](https://web.archive.org/web/20260316121248/https://people.eecs.berkeley.edu/~wkahan/ieee754status/IEEE754.PDF), if you want the\n   details of, for example, why NaN != NaN.\n   (One interesting comment discusses the value of 00, commonly debated online.\n   Kahan states, \"A consensus is growing that x\\*\\*0 = 1 for every x , including 0 , ∞ and NaN.\") [↩](#fnref:kahan \"Jump back to footnote 6 in the text\")\n7. The flowchart below shows the microcode that is shared for basic arithmetic instructions.\n   This flowchart calls the above flowchart as a subroutine.\n   The numerous special case paths are numbered.\n\n   [![Flowchart of the common microcode for basic arithmetic operations. (Click for a larger image.)](https://static.righto.com/images/8087-fadd/flowchart2-w600.jpg \"Flowchart of the common microcode for basic arithmetic operations. (Click for a larger image.)\")](https://static.righto.com/images/8087-fadd/flowchart2.jpg)\n\n   Flowchart of the common microcode for basic arithmetic operations. (Click for a larger image.)\n\n    [↩](#fnref:flowchart2 \"Jump back to footnote 7 in the text\")\n8. The actions of `SPECIAL_TMPS` are summarized below. It returns status through the `flag` flip-flop and the exponent converter register (`expConv`). Its actions are:\n\n   | Input | Result | flag | expConv |\n   | --- | --- | --- | --- |\n   | empty | NaN, exception | 1 | 1 |\n   | NaN | (larger) NaN, exception | 1 | 1 |\n   | infinity | infinity | 0 | 1 |\n   | denorm | unnorm | 1 | 0 |\n   | divide-by-zero(ish) | no change | 0 | 0 |\n\n   The line I call \"divide-by-zero(ish)\" indicates that a division\n   instruction is attempting to divide by zero or an unnormal number (which is close to zero).\n   I'll give a bit of background on how the 8087 represents 0 and values near 0.\n   A normal floating-point value has a mantissa with the first bit set.\n   Unfortunately, normal numbers have a large gap between the smallest normal number and zero.\n   Denormals break this \"rule\", allowing the top bits to be zero, so there are many more\n   numbers near 0.\n   (For a base-10 analogy, suppose you count down 1.003, 1.002, 1.001, 1.000. If the top\n   digit must remain set, the next possible number is 0, a huge jump from 1.000.\n   Denormals let you keep going in small steps: 0.999, 0.998, ...., 0.001, 0.)\n\n   For a denormal, the exponent has the smallest possible value. Unnormals are similar to\n   denormals, except the exponent has a larger value. The purpose of unnormals is that if\n   a denormalized exception is masked, the denormal number is converted to an unnormal\n   and execution continues.\n\n   For true zero values, the 8087 uses a signed zero, so you can have either +0 or -0.\n   A true zero is stored with the smallest possible exponent.\n   But the 8087 also supports a \"pseudo-zero\", a zero value that has a larger exponent.\n   The pseudo-zero can arise from multiplying two very small unnormal values.\n   (With the 80387 floating-point chip, Intel got rid of unnormals and pseudo-zeros.)\n\n   I discussed the microcode for the `SPECIAL_TMPS` subroutine in detail [earlier](https://www.righto.com/2026/09/8087-microcode-reverse-engineering-fscale.html),\n   so I won't discuss it more here. [↩](#fnref:special_tmps \"Jump back to footnote 8 in the text\")\n","body_html":"<p>In 1980, Intel introduced the 8087, a chip that made floating-point operations much faster\nin the IBM PC and other systems.\nAlthough the 8087 supports advanced operations such as tangents, square roots, and logarithms,\nthis article focuses on basic arithmetic operations: addition and subtraction.\nThe 8087 provided a spectacular speed improvement over the 8086 processor, speeding up\naddition by a factor of almost 100, and complicated operations by even more.<a href=\"#fn:speedup\">1</a>\nIn this article, I examine the 8087&#39;s microcode and circuitry to explain exactly how\nfloating-point addition and subtraction are implemented, including numerous special\ncases for infinities and other special values.</p>\n<p>To explore the 8087&#39;s circuitry, I popped the lid off a chip with a chisel\nand used a microscope to create the high-resolution image below.\nThe microcode ROM is the large rectangular region in the center of the die, holding the 1648 micro-instructions that control the chip.\nThe bottom half of the chip (red box) is the datapath, the circuitry that performs floating-point calculations on 80-bit values.<a href=\"#fn:bits\">2</a></p>\n<p><a href=\"https://static.righto.com/images/8087-fadd/8087-datapath-zoom-diagram.jpg\" rel=\"nofollow ugc noopener\"><img src=\"https://static.righto.com/images/8087-fadd/8087-datapath-zoom-diagram-w600.jpg\" alt=\"A close-up of the 8087&#39;s datapath, showing functional blocks that are used for addition and subtraction. Click this image (or any other) for a larger version.\" title=\"A close-up of the 8087&#39;s datapath, showing functional blocks that are used for addition and subtraction. Click this image (or any other) for a larger version.\" loading=\"lazy\" decoding=\"async\" referrerpolicy=\"no-referrer\" /></a></p>\n<p>A close-up of the 8087&#39;s datapath, showing functional blocks that are used for addition and subtraction. Click this image (or any other) for a larger version.</p>\n<p>Zooming in on the datapath shows the functional blocks that are important for\naddition and subtraction.\nThe key component is the integer adder, located in the center. It receives one argument from\nthe B register and another argument from a component such as the temporary registers or\nthe stack registers.\nThe adder&#39;s output is stored in the sum register.\nThe shifter can shift the bits in a value to the left or the right.\nThe shifter occupies a lot of space on the die because its circuitry allows a value to\nbe rapidly shifted by an arbitrary number of bits.\nFloating-point operations make heavy use of the shifter to align values; the leading zero\ncircuitry counts the number of zero bits or bytes on the left of a value so they can\nbe shifted away.\nThe exponent converter processes exponents, detecting\noverflows and underflows.\nThese specialized circuits helped the 8087 achieve its high performance.</p>\n<h2 id=\"floating-point-addition-and-subtraction\">Floating-point addition and subtraction</h2>\n<p>Most computers use integers as their basic representation for numbers.\nBut many applications need to use numbers with a fractional part, such as 1.23.\nMoreover, integers have a very limited range: a 32-bit integer can express numbers from\nabout -2 billion to 2 billion.\nTo solve these problems,<a href=\"#fn:problems\">3</a> computers usually implement floating-point numbers, which provide\nfractional numbers over an enormous range.</p>\n<p>In most cases, a floating-point number is represented by <em>sign</em> × <em>significand</em> × 2<em>exponent</em>.\nThe significand is a binary number of the form <code>1.bbb…</code>:\na leading 1, followed by the binary point (the binary equivalent of the decimal point) and the rest of the bits.<a href=\"#fn:leading-one\">4</a>\nIn a binary number, each bit represents a power of 2. For the fractional part, the part\nafter the binary point, the powers of 2 are negative: 2-1, 2-2,\nand so forth.\nSo the binary number 1.1 is 1 + 1/2, or 1.5 in decimal. Similarly, binary 1.0011 is\n1 + 1/8 + 1/16 or 1.1875.\nThe exponent allows numbers to escape the limited range of the significand.\nFor instance, 1.1 (binary) × 28 is 304.\nWith larger exponents, floating-point numbers can cover a huge range;\neven a 32-bit short floating-point number covers a range exceeding 10-36 to 1038.</p>\n<p>Floating-point addition and subtraction are more complicated than\ninteger operations. Before the numbers can be added (or subtracted), their binary points\nmust be aligned, the same as aligning the decimal points for decimal numbers.\nAfter the operation, the exponent of the result must be adjusted, normalizing the\nsignificand so it starts with a 1 bit.</p>\n<p><a href=\"https://static.righto.com/images/8087-fadd/add1.jpg\" rel=\"nofollow ugc noopener\"><img src=\"https://static.righto.com/images/8087-fadd/add1-w350.jpg\" alt=\"The steps for a simple floating-point addition.\" title=\"The steps for a simple floating-point addition.\" loading=\"lazy\" decoding=\"async\" referrerpolicy=\"no-referrer\" /></a></p>\n<p>The steps for a simple floating-point addition.</p>\n<p>The diagram above illustrates a simple floating-point addition: <code>13.5+3.5</code>.\nThe first two lines show the two arguments expressed with an exponent and significand.\nBoth arguments are normalized, so the leftmost bit is a 1.\nBefore adding, the second argument must be aligned—making the exponents equal—by shifting it two positions\nto the right. (The difference between the exponents in this case is two.)\nThe values are then added, using integer addition on the aligned significands.\nIn this case, adding the values causes an overflow, an &quot;extra&quot; bit\non the left.\nThe result must be normalized by shifting it one position to the right and incrementing the\nexponent by 1.</p>\n<p>Floating-point subtraction is similar, but has a few more complications.\nThe result can be positive, negative, or zero. The result can be orders of magnitude smaller than the arguments.\nThe figure below shows a basic subtraction, 8-7.5.\nAs with addition, the second argument is shifted right so the exponents match (i.e., the binary points line up). The two significands are subtracted with integer subtraction.\nThe result is normalized by shifting to the left so the top bit is a 1. Unlike the addition case, normalization may require a large shift.\nIn this case, the shift is four bits and the exponent is decremented by 4 to yield the result.</p>\n<p><a href=\"https://static.righto.com/images/8087-fadd/sub1.jpg\" rel=\"nofollow ugc noopener\"><img src=\"https://static.righto.com/images/8087-fadd/sub1-w320.jpg\" alt=\"A simple floating-point subtraction.\" title=\"A simple floating-point subtraction.\" loading=\"lazy\" decoding=\"async\" referrerpolicy=\"no-referrer\" /></a></p>\n<p>A simple floating-point subtraction.</p>\n<p>One complication with subtraction is that the result can be negative. If that occurs,\nthe significand is made positive, and the sign of the result is flipped.\nSubtraction can also result in zero. In this case, normalization cannot be performed; instead,\na special value +0 or -0 is returned as appropriate.</p>\n<h2 id=\"relevant-hardware-details-of-the-8087\">Relevant hardware details of the 8087</h2>\n<p>In this section, I&#39;ll explain some features of the 8087 that play important roles\nin the microcode implementation of addition and subtraction.</p>\n<p>The 8087 supports a variety of data types, but\ninternally, the 8087 stores numbers in an 80-bit format called &quot;temporary real&quot;.\nA temporary real number has a sign bit, a 15-bit exponent, and a 64-bit significand.\nThe 15-bit exponent allows numbers that are absurdly large, over 104932.\nThe 64-bit significand supports extremely accurate numbers.\nThe exponent is stored with a &quot;bias&quot; of 16383 added to it. Thus, the stored exponent is always positive, even if the real\nexponent is negative. For instance, an exponent of 1 is stored as <code>0x4000</code>, and an exponent of -16 is stored as <code>0x3fef</code>.</p>\n<p><a href=\"https://static.righto.com/images/8087-fadd/registers.jpg\" rel=\"nofollow ugc noopener\"><img src=\"https://static.righto.com/images/8087-fadd/registers-w550.jpg\" alt=\"The 80-bit temporary real format and the register formats. The dot indicates the binary point, analogous to the decimal point.\" title=\"The 80-bit temporary real format and the register formats. The dot indicates the binary point, analogous to the decimal point.\" loading=\"lazy\" decoding=\"async\" referrerpolicy=\"no-referrer\" /></a></p>\n<p>The 80-bit temporary real format and the register formats. The dot indicates the binary point, analogous to the decimal point.</p>\n<p>The 8087 has eight internal registers, organized as a stack.\nThe programmer uses these registers when programming the 8087.\nEach register holds an 80-bit floating-point number.\nTo optimize performance,\neach value in the register stack also has an associated &quot;tag&quot; value:\nzero, valid, special, or empty.\nA value of zero is tagged as <em>zero</em>.\nA &quot;normal&quot; floating-point value is tagged as <em>valid</em>. If the value is infinity, Not a Number (NaN), or a denormalized value,\nthen it is tagged as <em>special</em>.\nFinally, an <em>empty</em> tag indicates that a register does not hold a value; this allows detection of\nstack underflow (reading an empty register) or stack overflow (storing to a non-empty\nregister).</p>\n<p>The 8087 also has temporary registers that it uses internally.\n<code>tmpA</code> and <code>tmpB</code> are 80-bit registers, along\nwith two tag bits.\nHowever, <code>tmpC</code> is different: it doesn&#39;t have a sign, exponent, or tag.\nMoreover,\nthe significand is 68 bits; <code>tmpC</code> has an additional\nbit to prevent overflow and three additional low-order bits for rounding, known as\n<em>guard</em>, <em>round</em>, and <em>sticky</em>.</p>\n<p>In the microcode,\neach 16-bit micro-instruction performs one low-level operation.\nMany of the operations move data from one register to another.\nOther operations provide conditional jumps, subroutine calls, and returns.</p>\n<p>The heart of the 8087&#39;s processing is the 69-bit integer adder, which I discussed <a href=\"https://www.righto.com/2026/06/intel-8087-adder-reverse-engineered.html\" rel=\"nofollow ugc noopener\">here</a>.\n(The adder has more than 64 bits because it has three rounding bits and a couple of\nadditional high-order bits for overflow and multiplication.)\nInteger addition takes two micro-instructions. The first micro-instruction\nspecifies the first argument to add and selects various options such as\ncarry and rounding.\n(For subtraction, an option complements the second argument.)\nThe second argument to the adder always comes from the <code>B</code> register, which confusingly is\nunrelated to the <code>tmpB</code> register.\nThe result of the addition operation is placed in a special register called the sum register.\nA second micro-instruction (which may be many instructions after the first) moves the result from the sum register to a destination.</p>\n<p>Floating-point also makes extensive use of the bit shifter, which shifts a 68-bit value\nby an arbitrary number of positions, left or right; I wrote about it <a href=\"https://www.righto.com/2020/05/die-analysis-of-8087-math-coprocessors.html\" rel=\"nofollow ugc noopener\">here</a>.\nLike the adder, a bit shift operation takes two micro-instructions: the first moves a value to the shifter and\nconfigures the direction and amount of shift. The second micro-instruction\nmoves the result from the shifter to a destination.</p>\n<h2 id=\"the-addition-subtraction-microcode\">The addition/subtraction microcode</h2>\n<p>The 8087 implements almost all of its operations in microcode.\nThe basic math operations (addition, subtraction, multiplication,\nand division) start by executing the same microcode (at decimal address <code>#0001</code>.\nThe microcode moves the two arguments into temporary registers <code>tmpA</code> and <code>tmpB</code>.\nNormally, these arguments are taken from the stack registers. The arguments can also\nbe taken from memory, but I won&#39;t discuss that here.</p>\n<p>Next, the microcode does a micro-subroutine call to handle the specific operation type.\nThe 8087&#39;s microcode engine has a tiny ROM that holds a table of addresses, targets for microcode jumps or\nsubroutine calls.\nTarget 0 is special, however; it has three different addresses for multiplication, division,\nor addition/subtraction.\n(This is implemented by special-case circuitry in the ROM. When you&#39;re designing the chip, you can\ndo weird, hacky optimizations like this if you want.)</p>\n<p>The addition/subtraction micro-subroutine starts at address <code>#0232</code>; this microcode\nis shown below. The control flow is complicated, so I&#39;ve put a flowchart in the footnotes.<a href=\"#fn:flowchart\">5</a>\nThe microcode first handles the zero cases 0+0 or 0-0.\nNext, it subtracts the exponents of the two arguments.\nIf the first argument has a smaller exponent (i.e., the result is negative), the arguments\nare swapped so the argument with the larger argument is first.\n(The 8087 keeps track of the sign of the result in the &quot;sign latch&quot;; if exchanging\nthe arguments would change the sign, the latch is toggled at <code>#0244</code>.)</p>\n<pre><code>#0232 jmp #0236 if not tmpA:tag ZERO Check for 0+0\n#0233 jmp #0236 if not tmpB:tag ZERO\n#0234 return if not unsigned sub 0+0 same signs\n#0235 jmp #0263            handle 0+0 different signs\n#0236 tmpA sign -&gt; sign latch nonzero: tmpA sign to sign latch\n#0237 tmpB:exp -&gt; Breg\n#0238 adder: tmpA:exp - Breg cin=1 compare exponents\n#0239 jmp #0245 if adder pos\n#0240 sumreg:frac -&gt; Breg  swap if tmpB&#39;s exponent larger\n#0241 adder: zero - Breg cin=1\n#0242 xchg tmp\n#0243 jmp #0245 if not unsigned sub\n#0244 !sign latch -&gt; sign latch toggle sign if swapping matters\n#0245 xchg tmp             continue\n#0246 shift zero R 0 bytes, 0 bits zero\n#0247 call CREATE_DENORM   shift tmpB to match exponents\n#0248 xchg tmp\n#0249 zero -&gt; tmpB:exp     store exp adjustment in tmpB:exp\n#0250 jmp #0269 if not unsigned sub base addition?\n#0251 adder: tmpA:frac - Breg cin=1, roundmode tmp A - tmpB\n#0252 jmp #0256 if adder pos\n#0253 sumreg:frac,rnd -&gt; Breg/rnd negate frac\n#0254 adder: zero - Breg cin=1, roundmode\n#0255 !sign latch -&gt; sign latch toggle sign\n#0256 jmp #0275 if Sum register is zero jump if result zero\n#0257 shift R -&gt; tmpB:frac orig tmpB, from CREATE_DENORM\n#0258 jmp #0282 if tmpA top bit tmpA not denorm?\n#0259 jmp #0282 if tmpB:frac[63] tmpB not denorm?\n#0260 jmp #0289            skip normalization if both denorms\n#0261 tmpA:exp -&gt; expConv  handle zero\n#0262 jmp #0264 if not tmpA top bit\n#0263 zero -&gt; tmpA         handle zero cases\n#0264 0 -&gt; tmpA sign       +0\n#0265 zero -&gt; tmpA:frac\n#0266 return if not round chop\n#0267 1 -&gt; tmpA sign       return -0\n#0268 return\n#0269 adder: tmpA:frac + Breg cin=0, roundmode base addition: tmpA + tmpB\n#0270 jmp #0289 if not sumreg[64] check for overflow\n#0271 shift 1 sumreg:sign,frac,rnd R shift right 1 bit to normalize\n#0272 shift R -&gt; sumreg:frac,sign,rnd\n#0273 expAdj++             increment exp adjustment\n#0274 jmp #0289            wrap up addition\n#0275 shift sumreg:frac,rnd L 0 bytes, 3 bits zero? shift to get rounding bits\n#0276 shift L -&gt; sumreg:frac\n#0277 jmp #0261 if Sum register is zero jump if no rounding bits\n#0278 zero -&gt; sumreg:frac  clear result\n#0279 jmp #0289 if not tmpA top bit denorm?\n#0280 expAdj--             if not denorm, adjust exp down\n#0281 1 -&gt; sumreg:frac,rnd set lowest bit in result\n#0282 lzcount -&gt; shiftcountH leading zero adjust: handle bytes\n#0283 shift sumreg:frac,rnd L count byte sticky\n#0284 shift L -&gt; sumreg:frac,rnd\n#0285 lzcount -&gt; shiftcountL handle leading zero bits\n#0286 shift sumreg:frac,rnd L count bit sticky\n#0287 shift L -&gt; sumreg:frac,rnd\n#0288 shiftcount -&gt; tmpB:exp remember the bit shift amount\n#0289 sign latch -&gt; tmpA sign store sign\n#0290 call ADJUST_PRECISION if not FBLD/FBSTP adjust precision if necessary\n#0291 sumreg:frac -&gt; tmpA:frac store adjusted frac part\n#0292 tmpB:exp -&gt; Breg     get exp\n#0293 adder: tmpA:exp - Breg cin=0 adjust by Breg\n#0294 expAdj -&gt; Breg\n#0295 adder: sumreg:frac + Breg cin=0 adjust by expAdj\n#0296 sumreg:frac -&gt; expConv exp in expConv to check for overflow, etc\n#0297 sumreg:frac -&gt; tmpA:exp store exp in tmpA\n#0298 return               done</code></pre>\n<p>At address <code>#0247</code>, the important alignment step takes place, shifting <code>tmpB</code> so the\nbinary point lines up with <code>tmpA</code>. This is done by the microcode subroutine <code>CREATE_DENORM</code>.\nThe number of positions to shift <code>tmpB</code> is passed in the sum register; the difference\nbetween the two exponents was calculated in the previous subtraction.\n(If the difference was negative, it was complemented, so the value passed in is positive.)</p>\n<p>Typically, the exponent of the result will be the same as the exponent of the (larger) value,\nbut not always.\nChanges to the exponent are tracked in two places. First, a special two-bit register that I\ncall the exponent adjustment (<code>expAdj</code>) holds adjustments due to one-bit shifts left or\nright. The hardware initializes the <code>expAdj</code> register to 1 so there is room for a decrement.\nLarger exponent changes are tracked in the <code>tmpB</code> register&#39;s exponent field;\nthe microcode initializes it to 0 at <code>#0249</code>.</p>\n<p>At address <code>#0250</code>, the microcode makes a key decision: is the low-level operation\nunsigned addition\nor unsigned subtraction?\nDepending on the signs involved, the high-level operation (addition or subtraction) may\nnot match the low-level operation.\nFor example, for 2+3, -2-3, -2+(-3), and so forth, the two unsigned numbers are added; the result is 5, either positive or negative.\nBut for -2+3, 2-3, 3-2, and so forth, the two unsigned numbers are subtracted; the result is 1, either positive or negative.\nI&#39;ll use &quot;unsigned addition&quot; to refer to the former cases, where the unsigned magnitudes are added,\nand &quot;unsigned subtraction&quot; to refer to the latter cases, where the unsigned magnitudes are subtracted.\nThe key point is that an addition instruction may result in unsigned addition or subtraction,\nand the same with a subtraction instruction.</p>\n<p>The 8087 has specialized circuitry to determine whether the low-level unsigned operation is addition or subtraction, allowing this to be tested with a conditional.\nThe <code>tmpA</code> and <code>tmpB</code> values have sign bits of 1 for negative, 0 for positive.\nIf you assign 1 to subtraction and 0 to addition, then <code>tmpAsign⊕​tmpBsign⊕op</code> yields 1 if the unsigned operation is subtraction and 0 if the unsigned operation is addition.\nThe 8087 also has a conditional to test if the signs are the same or different.</p>\n<h3 id=\"unsigned-addition\">Unsigned addition</h3>\n<p>Since the unsigned addition path is simpler, I&#39;ll start with it, at address <code>#0269</code>.\nThe steps are essentially the same as described earlier: aligning the numbers, adding them,\nand normalizing.\nFirst, the smaller number, in <code>tmpB</code>, is shifted right until the exponents match.\nThen the (unsigned) values are added.\nIf there is a carry from the top bit, the result is shifted right at <code>#0271</code>.\nBecause the result is shifted right one bit,\nthe special two-bit <code>expAdj</code> counter is incremented to track the required exponent adjustment.</p>\n<p>At <code>#0289</code>, the result&#39;s sign is updated with the value in the sign register.\nThe 8087 supports three sizes of values: 80-bit temporary reals, 64-bit reals, or 32-bit\nreals.\nFor the smaller sizes, the microcode subroutine <code>ADJUST_PRECISION</code> trims the value to the\nappropriate size.\n(This is not as trivial as it sounds, since rounding up the result could\nrequire an adjustment to\nthe result&#39;s exponent, recorded in the <code>expAdj</code> register.)\nFinally, the result&#39;s exponent is calculated from the initial exponent (in <code>tmpA</code>),\nsubtracting the adjustment value in <code>tmpB</code>&#39;s exponent (normally 0), adding the\nadjustment value in the <code>expAdj</code> register, and subtracting 1 (because <code>expAdj</code> starts at 1).\nThe exponent is stored in the result but also stored in the &quot;exponent converter&quot;,\na circuit that evaluates the exponent to detect overflow or underflow.</p>\n<p>At this point, the addition/subtraction micro-subroutine returns to the common microcode.\nThe common microcode checks if there was an overflow or underflow, indicating the value\ndoesn&#39;t fit in a float.\nIn this case, the subroutine <code>NONNORMAL_RESULT</code> is called to handle the value, which can involve\ntriggering an interrupt, returning infinity, or returning zero.\n(For details, see my <a href=\"https://www.righto.com/search?q=nonnormal_result#:~:text=Handling%20a%20non%2Dnormal%20result\" rel=\"nofollow ugc noopener\">previous article</a>.)</p>\n<p>The result is pushed on the stack by writing it to the appropriate stack register.\nIf the original instruction specified a stack pop at the end, the top of stack is marked\nempty, and the stack pointer is updated.\nFinally, the Run Next Instruction micro-instruction indicates the end of the microcode\nfor the current instruction. The floating-point instruction has finally completed.</p>\n<h3 id=\"unsigned-subtraction\">Unsigned subtraction</h3>\n<p>I&#39;ll switch now to the unsigned subtraction path, starting at <code>#0251</code>.\n(Keep in mind that the instruction could be addition, for example, -2 + 3.)\nThe microcode uses the 64-bit adder to perform an unsigned subtraction: <code>tmpA-tmpB</code>;\nthe second argument is negated to perform subtraction.\nThe result can be either positive or negative, depending on which value is larger.\nThe rest of the path is the same as addition: setting the sign, adjusting the precision, and computing the final exponent.</p>\n<p>If the subtraction result is zero, <code>#0275</code> tests the three\nrounding bits. If a rounding bit is set, then the low bit\nof the result is set, and the exponent adjustment is decreased by 1 to\ncounteract this.\n(For a denorm, the result is cleared since there&#39;s no &quot;extra&quot; room to promote the rounding bit.)\nThere&#39;s a special path <code>#0261</code> if the result is really zero, because the microcode needs to determine if the result is +0 or -0.\nSubtracting 0-0 joins into this code path to handle the various possibilities for adding or subtracting +0 and/or -0.\nGenerally, positive and negative zeros work as you&#39;d expect from a zero that &quot;leans&quot; one way.\nFor instance, ⁺0 + ⁺0 is ⁺0 and ⁻0 - ⁺0 = ⁻0.\nBut what if the zeros cancel? E.g.\n⁺0 + ⁻0 or ⁻0 - ⁻0? In these cases, rounding down yields ⁻0 while the other rounding modes yield ⁺0.</p>\n<p>The leading zero circuit counts the number of leading zeros to determine how much to shift.\nTo keep the circuitry manageable, the leading zero shift is done in two steps.\nFirst, the result is shifted left by the number of leading zero bytes.\nNext, the leading zero circuitry counts the number of remaining leading zero bits, which will be 0 to 7 at this point.\nThe result is shifted left by that many bits.\nThe total shift amount is stored in <code>tmpB</code>&#39;s exponent.</p>\n<p>The unsigned subtraction path merges at <code>#0289</code> with the unsigned addition path described earlier,\nadjusting the precision, computing the exponent, and storing the result.\nThe micro-subroutine then returns to the common code that also handles multiplication and division.</p>\n<h3 id=\"special-case-handling-in-the-common-microcode\">Special-case handling in the common microcode</h3>\n<p>The 8087 handles many special cases for infinities, zeros, Not-a-Number, denormals,\nand other special values.\nThe designers of the 8087 carefully thought about these special cases and attempted\nto handle them in mathematically reasonable ways.<a href=\"#fn:kahan\">6</a>\nThe table below is just part of the special cases that the 8087 handles.</p>\n<p><a href=\"https://static.righto.com/images/8087-fadd/infinities.jpg\" rel=\"nofollow ugc noopener\"><img src=\"https://static.righto.com/images/8087-fadd/infinities-w400.jpg\" alt=\"Part of the special-case handling of infinities as described in the User&#39;s Manual.\" title=\"Part of the special-case handling of infinities as described in the User&#39;s Manual.\" loading=\"lazy\" decoding=\"async\" referrerpolicy=\"no-referrer\" /></a></p>\n<p>Part of the special-case handling of infinities as described in the <a href=\"https://archive.org/details/8086familyusersm00inte/page/n85/mode/2up\" rel=\"nofollow ugc noopener\">User&#39;s Manual</a>.</p>\n<p>While these special cases make the 8087 more accurate mathematically, they\nmake the microcode much more complicated.\nThe microcode below is common to addition, subtraction, multiplication, and division.\nIt loads arguments and stores the result as described above, but for the most part,\nit handles special cases.\nThis microcode is &quot;spaghetti code&quot;, with 18 conditional branches in a row to handle numerous\nspecial cases.\nI&#39;ll describe it at a high level here; the flowchart in the footnotes<a href=\"#fn:flowchart2\">7</a> provides details.</p>\n<pre><code>#0001 call MEM_OP if mem op maybe get args from memory\n#0002 jmp #0004 if mem op\n#0003 st(i) -&gt; tmpB        get from stack\n#0004 jmp #0018 if DE (denormalized) interrupt quit if denormalized interrupt\n#0005 st(0) -&gt; tmpA        top of stack to tmpA\n#0006 jmp #0008 if not reverse bit no jump if FSUBR, FMUL, FDIVR?\n#0007 xchg tmp             reverse arguments\n#0008 jmp #0019 if tmp anomalous handle anomalous values\n#0009 call INSTR_SWITCH    switch on instruction type: J1(mul), J2(add/sub), J3(div)\n#0010 call NONNORMAL_RESULT if not exp normal Back, finish up instruction\n#0011 jmp #0014 if dst st(i) test d destination bit\n#0012 tmpA -&gt; st(0)        destination st(0)\n#0013 RNI\n#0014 tmpA -&gt; st(i)        exit: destination st(i)\n#0015 jmp #0018 if not pop at end RNI if not pop\n#0016 empty -&gt; SP(0) tag   mark top of stack empty\n#0017 stackPtr++           pop\n#0018 RNI\n#0019 call SPECIAL_TMPS    handle special values\n#0020 jmp #0023 if not flag\n#0021 jmp #0011 if expConv[0] jump to finish\n#0022 jmp #0009            switch on instruction type\n#0023 cond3b -&gt; sign latch more conditions\n#0024 jmp #0039 if expConv[0]\n#0025 jmp #0032 if not tmpB:tag ZERO\n#0026 jmp #0035 if tmpA:tag ZERO\n#0027 except:zero div\n#0028 NaN -&gt; tmpA          NaN, finish\n#0029 high bit -&gt; tmpA:frac\n#0030 sign latch -&gt; tmpA sign\n#0031 jmp #0037            finish up\n#0032 jmp #0035 if not tmpA:tag ZERO divide by denorm/0\n#0033 tmpB:frac -&gt; sumreg:frac divide by 0\n#0034 jmp #0055 if not Sum register is zero\n#0035 except:invalid\n#0036 NaN -&gt; tmpA\n#0037 jmp #0018 if intr    jump to RNI\n#0038 jmp #0011            finish\n#0039 jmp #0051 if not tmpA:tag SPECIAL more conditions\n#0040 jmp #0046 if not tmpB:tag SPECIAL\n#0041 jmp #0028 if FMUL\n#0042 jmp #0035 if FDIV FDIVR\n#0043 jmp #0035 if not affine ∞ ∞×∞: test if affine\n#0044 jmp #0035 if unsigned sub test if ∞ add or subtract\n#0045 jmp #0038\n#0046 jmp #0049 if not FMUL\n#0047 jmp #0035 if tmpB:tag ZERO\n#0048 jmp #0028\n#0049 jmp #0028 if FDIV FDIVR\n#0050 jmp #0038\n#0051 jmp #0054 if not FMUL\n#0052 jmp #0035 if tmpA:tag ZERO divide by 0?\n#0053 jmp #0028\n#0054 jmp #0057 if not FDIV FDIVR\n#0055 zero -&gt; tmpA         0÷denorm or n÷∞\n#0056 jmp #0030            do something and finish up\n#0057 xchg tmp\n#0058 jmp #0061 if sign mismatch\n#0059 jmp #0062 if unsigned sub test if adding or subtracting\n#0060 jmp #0038            finish\n#0061 jmp #0038 if unsigned sub finish\n#0062 tmpA sign -&gt; sign latch complement sign\n#0063 !sign latch -&gt; tmpA sign\n#0064 jmp #0038            finish</code></pre>\n<p>Looking at <code>#0008</code>, the 8087 micro-architecture includes a conditional test that will check if either temporary\nregister is empty or is tagged with a special value.\nIf so, a subroutine that I call <code>SPECAL_TMPS</code> is called.<a href=\"#fn:special_tmps\">8</a>\nIt examines the two temporary registers and sets two flags to indicate what special\nhandling needs to be done.</p>\n<p>If both flags are set, one of the arguments was tagged empty, typically indicating a stack underflow. In this case, the microcode returns Not-a-Number (NaN).\nIf the second flag is clear, then one of the arguments was a denormal number, a special value very close to zero. The arithmetic operation continues in that case.</p>\n<p>The remaining code paths get complicated, for example, branching on the signs of the arguments,\nthe specific arithmetic operation, or if an argument is zero or infinity.\nEight code paths return Not-a-Number:\nn÷denorm, 0÷0, 0÷pseudo-zero, ∞×0, ∞÷∞, 0×∞, ∞ op ∞ proj, and ∞-∞ affine.\nMany of these are straightforward—0×∞ has no meaningful value, for instance—but the\nlast two need a bit more explanation.</p>\n<p>The 8087 has two different modes for infinity, selected by a bit in the control register.\nWith &quot;affine closure&quot;, infinity is signed, so +∞ and -∞ are two different values.\nThe recommended mode is &quot;projective closure&quot;, where infinity is unsigned.\nIn affine mode, ∞+∞ is perfectly valid, yielding positive infinity.\nHowever, ∞-∞ is meaningless; an answer of 0 would be arbitrary, so NaN is returned.\nIn projective mode, none of the operations on infinity make sense because infinity is both\ninfinitely positive and infinitely negative.</p>\n<p>The microcode has eight different paths to return infinity:\nn÷0, ∞×n, ∞×∞, n×∞, ∞÷x, x±∞, ∞±x, and ∞+∞ affine.\n(The first case assumes that the divide-by-zero exception has been masked.)\nFor the first five cases, the microcode generates the sign for infinity by combining the\nsigns of the arguments.\nHowever, the last three cases take the sign from the infinity argument and ignore the other\nsign. In other words, multiplication and division can flip the sign of an infinity, but\naddition and subtraction can&#39;t.</p>\n<p>Two paths deal with special-case 0 results: 0÷denorm and n÷∞.\nThe microcode generates 0 with the correct sign, based on the signs of the arguments.</p>\n<h2 id=\"the-leading-zeros-circuitry\">The leading zeros circuitry</h2>\n<p>With subtraction, digits can cancel out, leaving leading zeros that need to be removed\nby shifting the value.\nThe 8087&#39;s shifter can shift a 64-bit value by an arbitrary number of bits.\nThe job of the leading-zero circuitry is to count the leading zeros to determine how\nmuch to shift.\nThat is, it counts how many zeros are to the left of the first 1 bit and turns this\ninto a binary value.\nThis is basically the same as a priority encoder that encodes the position of the first 1 bit.</p>\n<p>Leading-zero circuits are somewhat complicated because each binary output bit depends on\nall the input bits; a 64-bit detector has 64 bits coming in.\nOne approach is to take the truth table and optimize it, ending up with a pile of logic.\nAnother approach is to use a hierarchical approach, a tree with log(N) stages.\nThe 8087 takes a simpler path: one pass to eliminate leading zeros a byte at a time,\nand a second pass to eliminate the remaining 0 to 7 leading zero bits.\nEach pass requires a separate 8-input leading zero detector circuit.\nThis approach is slower, but the circuitry is much simpler: two 8-bit leading zero detectors\ninstead of a 64-bit leading zero detector.</p>\n<p>The schematic below shows the circuit that converts 0 to 7 leading zeros into a 3-digit\nbinary value.\nTo simplify the schematic, I&#39;ve replaced the first layer of logic with a bitmask that represents\nthe 8-bit input.\nFor instance, <code>x01xxxxx</code> indicates that the signal is high if the second bit is 0, the\nthird bit is 1, and the remaining bits are arbitrary.\nI&#39;ve added text descriptions; due to optimizations, the descriptions have some exceptions.\nFor instance, <code>x01xxxxx</code> matches anything with two leading zeros.\nIt also matches some patterns with no leading zeros, but those don&#39;t change the result.\nThe important thing is that it matches <em>everything</em> with 2 leading zeros and <em>nothing</em> with\n1 or 3 leading zeros.</p>\n<p><a href=\"https://static.righto.com/images/8087-fadd/leading-zeros.jpg\" rel=\"nofollow ugc noopener\"><img src=\"https://static.righto.com/images/8087-fadd/leading-zeros-w600.jpg\" alt=\"The circuitry to detect 0 to 7 leading zero bits. The same approach is used to detect leading zero bytes.\" title=\"The circuitry to detect 0 to 7 leading zero bits. The same approach is used to detect leading zero bytes.\" loading=\"lazy\" decoding=\"async\" referrerpolicy=\"no-referrer\" /></a></p>\n<p>The circuitry to detect 0 to 7 leading zero bits. The same approach is used to detect leading zero bytes.</p>\n<p>The output from the circuit is a 3-bit binary value representing the number of leading zeros.\nThe top NOR gate generates the low bit, set for 1, 3, 5, or 7 leading zeros.\nThe high bit is generated by the bottom NOR gate: set for 4, 5, 6, or 7.\nThe middle bit is trickier, with an additional layer of logic to determine if there are 4 or 5 leading zeros\n(or maybe 0 or 1). The two 1 cases block four zeros, while the 00 pair\nmeans that four leading zeros would turn into at least 6.\n(This is a hand-waving explanation, but one can enumerate the 256 cases and verify\nthat they all produce the correct answer.)</p>\n<p>To count the number of <em>bytes</em> with leading zeros, similar logic is used.\nHowever, each group of 8 bits goes through an OR gate to detect if any bits are\nset in the byte. Thus, the logic determines how many bytes of leading zeros are present.</p>\n<p>On the die, the leading zero logic is immediately to the left of the sum register, examining each\nbit of the sum register.</p>\n<h2 id=\"conclusions\">Conclusions</h2>\n<p>Most people don&#39;t give much thought to computer operations such as addition or subtraction.\nBut there are interesting algorithms and special cases behind the scenes, and\nthese features are embodied in clever silicon circuits.\nThe intricate rules to handle positive and negative zeros, positive and negative infinities,\nand Not-a-Number are part of modern computers, since the 8087 led to the IEEE-754 floating-point\nstandard used today.\nThe 8087 went a bit overboard with special cases, however.\nSome 8087 features, such as projective infinity, unnormals, and pseudo-zero\nwere dropped from the standard becauses they weren&#39;t worth the effort.</p>\n<p>I plan to continue reverse-engineering the 8087 microcode;\nfor updates, follow me on\nBluesky (<a href=\"https://bsky.app/profile/righto.com\" rel=\"nofollow ugc noopener\">@righto.com</a>),\nMastodon (<a href=\"https://oldbytes.space/@kenshirriff\" rel=\"nofollow ugc noopener\">@[email protected]</a>),\nor <a href=\"https://www.righto.com/feeds/posts/default\" rel=\"nofollow ugc noopener\">RSS</a>.\nI&#39;ve been working on this with the members of the &quot;Opcode Collective&quot;, especially Smartest Blob and Gloriouscow, who converted the\nROM images to microcode data and extensively analyzed the contents.\nSee the <a href=\"https://github.com/a-mcego/granite/tree/main/tools/8087mc\" rel=\"nofollow ugc noopener\">8087 repository</a> on GitHub for more.\nAI statement: Despite the presence of the em dash, no AI was used in the writing of this article (<a href=\"https://www.righto.com/p/index.html#ai\" rel=\"nofollow ugc noopener\">details</a>). The Opcode Collective used an ML algorithm to classify each ROM cell to extract the microcode.</p>\n<h2 id=\"notes-and-references\">Notes and references</h2>\n<ol><li><p>Intel&#39;s <a href=\"https://archive.org/details/8086familyusersm00inte/page/n16/mode/1up]\" rel=\"nofollow ugc noopener\">documentation</a> provides a performance comparison of the 8087 versus floating-point operations emulated on the 8086.</p><p> The speedup varies depending on the instruction, but is typically almost a factor of 100.\n This speedup made the 8087 chip highly desirable for numerical applications such as CAD and spreadsheets. <a href=\"#fnref:speedup\" title=\"Jump back to footnote 1 in the text\">↩</a></p></li><li><p>Internally, the 8087 uses 80 bits to hold floating-point values: one bit for the sign,</p><p> 15 bits for the exponent, and 64 bits for the significand.\n However, the 8087 uses three extra low-order bits for rounding, called Guard, Round, and Sticky. These bits ensure that a value is always rounded in the right direction.\n Some parts of the datapath have additional bits for sign or overflow: the shifter is 68 bits wide, and the <a href=\"https://www.righto.com/2026/06/intel-8087-adder-reverse-engineered.html\" rel=\"nofollow ugc noopener\">adder</a> is 69 bits wide.\n For the most part, I&#39;ll ignore these extra bits and refer to the datapath as 64 bits wide. <a href=\"#fnref:bits\" title=\"Jump back to footnote 2 in the text\">↩</a></p></li><li><p>While floating-point numbers solve some problems, they introduce problems of their own.</p><p> For instance, 0.01 can&#39;t be expressed exactly in floating point (it has an infinite\n repeating fraction in binary, just like 1/3 can&#39;t\n be expressed exactly as a decimal number). This can cause havoc if you&#39;re using\n floating-point numbers to represent money and you&#39;re dealing with cents.\n For more on problems with floating-point, see <a href=\"https://jvns.ca/blog/2023/01/13/examples-of-floating-point-problems/\" rel=\"nofollow ugc noopener\">this Julia Evans comic</a> or <a href=\"https://perso.ens-lyon.fr/jean-michel.muller/goldberg.pdf\" rel=\"nofollow ugc noopener\">What Every Computer Scientist Should Know About\n Floating-Point Arithmetic</a>. <a href=\"#fnref:problems\" title=\"Jump back to footnote 3 in the text\">↩</a></p></li><li><p>One difference between the 8087&#39;s temporary real format and the external formats is that</p><p> in the temporary real, the leading 1 digit is explicitly stored in the number.\n In the external formats, the leading 1 is implicit and not stored in the number,\n providing one additional bit of accuracy, but making arithmetic more complicated. <a href=\"#fnref:leading-one\" title=\"Jump back to footnote 4 in the text\">↩</a></p></li><li><p>The flowchart below shows the control flow for the addition/subtraction microcode.</p><p> (Click it for a larger image.)</p>\n<p> <a href=\"https://static.righto.com/images/8087-fadd/flowchart.jpg\" rel=\"nofollow ugc noopener\"><img src=\"https://static.righto.com/images/8087-fadd/flowchart-w400.jpg\" alt=\"Flowchart of the add/sub microcode.\" title=\"Flowchart of the add/sub microcode.\" loading=\"lazy\" decoding=\"async\" referrerpolicy=\"no-referrer\" /></a></p>\n<p> Flowchart of the add/sub microcode.</p>\n<p>  <a href=\"#fnref:flowchart\" title=\"Jump back to footnote 5 in the text\">↩</a></p></li><li><p>William Kahan is the floating-point expert behind the 8087. He wrote about</p><p> the motivations for various design decisions <a href=\"https://web.archive.org/web/20260316121248/https://people.eecs.berkeley.edu/~wkahan/ieee754status/IEEE754.PDF\" rel=\"nofollow ugc noopener\">here</a>, if you want the\n details of, for example, why NaN != NaN.\n (One interesting comment discusses the value of 00, commonly debated online.\n Kahan states, &quot;A consensus is growing that x**0 = 1 for every x , including 0 , ∞ and NaN.&quot;) <a href=\"#fnref:kahan\" title=\"Jump back to footnote 6 in the text\">↩</a></p></li><li><p>The flowchart below shows the microcode that is shared for basic arithmetic instructions.</p><p> This flowchart calls the above flowchart as a subroutine.\n The numerous special case paths are numbered.</p>\n<p> <a href=\"https://static.righto.com/images/8087-fadd/flowchart2.jpg\" rel=\"nofollow ugc noopener\"><img src=\"https://static.righto.com/images/8087-fadd/flowchart2-w600.jpg\" alt=\"Flowchart of the common microcode for basic arithmetic operations. (Click for a larger image.)\" title=\"Flowchart of the common microcode for basic arithmetic operations. (Click for a larger image.)\" loading=\"lazy\" decoding=\"async\" referrerpolicy=\"no-referrer\" /></a></p>\n<p> Flowchart of the common microcode for basic arithmetic operations. (Click for a larger image.)</p>\n<p>  <a href=\"#fnref:flowchart2\" title=\"Jump back to footnote 7 in the text\">↩</a></p></li><li><p>The actions of <code>SPECIAL_TMPS</code> are summarized below. It returns status through the <code>flag</code> flip-flop and the exponent converter register (<code>expConv</code>). Its actions are:</p><div class=\"table-wrap\"><table><thead><tr><th>Input</th><th>Result</th><th>flag</th><th>expConv</th></tr></thead><tbody><tr><td>empty</td><td>NaN, exception</td><td>1</td><td>1</td></tr><tr><td>NaN</td><td>(larger) NaN, exception</td><td>1</td><td>1</td></tr><tr><td>infinity</td><td>infinity</td><td>0</td><td>1</td></tr><tr><td>denorm</td><td>unnorm</td><td>1</td><td>0</td></tr><tr><td>divide-by-zero(ish)</td><td>no change</td><td>0</td><td>0</td></tr></tbody></table></div>\n<p> The line I call &quot;divide-by-zero(ish)&quot; indicates that a division\n instruction is attempting to divide by zero or an unnormal number (which is close to zero).\n I&#39;ll give a bit of background on how the 8087 represents 0 and values near 0.\n A normal floating-point value has a mantissa with the first bit set.\n Unfortunately, normal numbers have a large gap between the smallest normal number and zero.\n Denormals break this &quot;rule&quot;, allowing the top bits to be zero, so there are many more\n numbers near 0.\n (For a base-10 analogy, suppose you count down 1.003, 1.002, 1.001, 1.000. If the top\n digit must remain set, the next possible number is 0, a huge jump from 1.000.\n Denormals let you keep going in small steps: 0.999, 0.998, ...., 0.001, 0.)</p>\n<p> For a denormal, the exponent has the smallest possible value. Unnormals are similar to\n denormals, except the exponent has a larger value. The purpose of unnormals is that if\n a denormalized exception is masked, the denormal number is converted to an unnormal\n and execution continues.</p>\n<p> For true zero values, the 8087 uses a signed zero, so you can have either +0 or -0.\n A true zero is stored with the smallest possible exponent.\n But the 8087 also supports a &quot;pseudo-zero&quot;, a zero value that has a larger exponent.\n The pseudo-zero can arise from multiplying two very small unnormal values.\n (With the 80387 floating-point chip, Intel got rid of unnormals and pseudo-zeros.)</p>\n<p> I discussed the microcode for the <code>SPECIAL_TMPS</code> subroutine in detail <a href=\"https://www.righto.com/2026/09/8087-microcode-reverse-engineering-fscale.html\" rel=\"nofollow ugc noopener\">earlier</a>,\n so I won&#39;t discuss it more here. <a href=\"#fnref:special_tmps\" title=\"Jump back to footnote 8 in the text\">↩</a></p></li></ol>","headings":[{"level":2,"text":"Floating-point addition and subtraction","id":"floating-point-addition-and-subtraction"},{"level":2,"text":"Relevant hardware details of the 8087","id":"relevant-hardware-details-of-the-8087"},{"level":2,"text":"The addition/subtraction microcode","id":"the-addition-subtraction-microcode"},{"level":3,"text":"Unsigned addition","id":"unsigned-addition"},{"level":3,"text":"Unsigned subtraction","id":"unsigned-subtraction"},{"level":3,"text":"Special-case handling in the common microcode","id":"special-case-handling-in-the-common-microcode"},{"level":2,"text":"The leading zeros circuitry","id":"the-leading-zeros-circuitry"},{"level":2,"text":"Conclusions","id":"conclusions"},{"level":2,"text":"Notes and references","id":"notes-and-references"}]}}