Floats on x86 - The Story
Disclaimer: everything here is ‘as far as I know’. The web has information but deep buried. All information targeted toward Linux coding on Intel‐style 64‐bit x86 chips.
Floats. They arrived during the life of the x86 instruction set. What’s there and what is not is inconsistent and confusing. Or perhaps, given the circumstances, it’s good, but not what would have been provided from a clean slate. Let’s cover the basics. You’ll find them everywhere, but the information is scattered,
What are floating point numbers?
Commonly, and implicit in what you will often read, floats are numbers that can express a fraction. So, snippets of code from the NASM manual,
dd 1.234567e20 ; floating-point constant
dq 0x123456789abcdef0 ; eight byte constant
That’s a start, but it’s not the whole story. There are many kinds of representation of floats. To start with, some coders used fixed points, ‘it will always have four numbers after the point’, which like any system has advantages—I guess easy to code, efficient in space, and reliable in edge cases. But the different representations lead to many systems. There is some solid reporting online that coders felt there should be general agreement. People had done more abstract/theoretical work on this when Intel considered building their x87 chips. So much so, that Intel, whiles not showing their build process or details, joined in cross‐party talk to decide how float representation should be managed.
This lead to the IEEE 754 standard. This standard is important because it stuck—it’s used by most computing you or me might come across. The standard is very detailed and not fully implemented everywhere, but you and me don’t need to know about that.
Anyway, the IEEE 754 system works by specifying a number as the base, then the number itself (also called a ‘significand’, ‘mantissa’, or ‘coefficient’ if people want to sound mathematical or so forth) then an exponent (also called the ‘characteristic’, or ‘scale’). And the exponent has a sign too.
Here’s a much used ‘basic’ 32‐bit form,
22 bits
the number
1 bit
The round number, for programming the number up or down for rounding
1 bit
sign
8 bits
exponent
The above code results in a number size ‘precision’ of 24 bits. That’s a number you might recognise, even from a household retailer. It looks like 23 bits, but there’s an assumption made about a missing bit. This is called ’normalisation‘/’leading bit convention‘/‘the implicit bit convention’/‘the hidden bit convention‘. I’m not going into that. There are more parts to the spec, including ‘Not A Number’ definitions, ways to indicate errors, infinity handling and more—I’m not going into those either.
There’s an upshot,
Floats for big numbers
Float number forms don’t only express ‘fractions’. They have exponents, so can express ‘very big numbers’. They can’t express all very big numbers, and they do this with limited precision, but they can express numbers without the usual limits of bit size.
You may wonder, ‘is an important point?’ and I’m not the person to ask. That said, it is important to some. Important enough, for example, that, (as far as I know not using float, but the general idea), the language Python since 2025 has integrated big number capability because,
Having the machine word size exposed to the language hinders portability.
and any language with thoughts of numerical computing comes with libraries for big numbers, direct representations of fractions/rational numbers, and other numerical representations.
The float standard in practice
There’s thoughts worth being aware of in terms of ‘what I need to code’
x87 floats are implemented on a separate chip
…even if the chip is now inside every x86 chip. I guess my readers know this. Still, the chips have separate registers and their own, usually more limited and specialist, instruction sets
Floating point numbers sometimes need extra calculations to check and calculate
The instructions are slower than integers, hence the special chips and instruction sets
They have limited precision, so rounding errors are inevitable
There are provisions built into the standard, but mostly you don’t need to think unless you are doing something that needs the precision i.e. most of us don’t need to know
number ‘base’ representations are rarely used, so no need to think about them
In practice usually two, unless you have special needs
Also, as always in computer code, order of calculation matters. You’ll preserve more accuracy by doing expanding calculations first (rather than shrinking calculations). As an example, in C,
float x = 2.432;
x = x * 2;
x = x/3;
is likely more accurate than,
float x = 2.432;
x = x/3;
x = x * 2;
This applies particularly to floats as there will be rounding errors anyhow.
History of x87 float implementation
This is a ‘howto’ guide, so I wouldn’t usually clutter with talk of history. But for floats, it helps to know because,
The levels of implementation are still available
the x87 system has remained backward‐compatible over fifty years of computing
As a coder you’ll need to know what’s possible
especially how information from throughout the web is applicable
You’ll use some of these older systems
the thoughts behind float systems have moved towards media. The aim is not ‘bigger registers so better accuracy’, it is ‘more numbers in the registers, calculations working simultaneously’. So…
Floats involve thinking about sizing
coders deal direct with the sizing, not ‘use whatever is current’
Floats as early‐implemented
The float chip was called the ‘x87’ chip, presumably named after the ‘x86’. The original x87 floating‐point unit was optional—a customer bought one if they wanted. For and after 486 chips, the floating point chip was builtin.
The x87 was very early, at the time of the original 8086 and 8088, date 1980, 16‐bit chips. The original chips had eight registers (you’ll also see these referred to as ‘fpX’ registers),
st1
...
st7
The registers were 80‐bit wide. I can’t find anything definitive, but assume in System V call convention they are classed as ‘volatile’. The chips came with a set of new instructions for using the registers.
The registers were treated as a stack, and so were the instructions. To code these chips, numbers were taken from memory and pushed onto the stack, manipulated, then popped off. Note this, because it explains some oddities in the instruction set.
Hold up,
80‐bit wide registers?
Date of the x87 announcement was 1980. At that time computers were 16‐bit. 32‐bit was five years away. Best explanation I’ve read is that Intel were keen to get the unit right, so they pushed engineering to the limit.
And the other oddity—‐80‐bit is not a computery number, so where did that come from? Best explaination I’ve read is that 80 = 64 + 16—the 16‐bit extension was added to allow float exponents to sit in, so the number would be 64‐bits wide—that would reduce rounding errors on arithmetic.
Floats as later implemented
There’s too much of this for an overview article. But, as explained, it was and is relevant. In brief,
MMX
Never been proved the name stood for anything, though there was evidence it meant ‘Matrix Math Extensions’. At the time of the Pentium, 1997, a 32‐bit chip.
A change in handling of the original float registers. So,
st1
...
st7
were treated as 64‐bit wide (so gave up on the extra 16 bits of extension). Again, I assume they were ‘volatile’. The new instructions allowed several smaller integers to be stored in the registers and worked on simultaneously. So not floats at all, used for multiple value handling. Another change was the instructions stopped treating the registers as a stack, MMX instructions could access the registers randomly.
Despite the parallelism, the more accessible registers and the flexible number conversions, reports say MMX didn’t get much uptake. Specialist graphics chips were popular at the time, and doing this job more comprehensively.
SSE
Originally, ‘Streaming SIMD Extensions’. At the time of the Pentium III, 1999, a late 32 bit chip. Close after AMD’s 3DNow! (SSE was the big feature of the Pentium III). First 8 but quickly 16 new registers,
xmm0
...
xmm15
128‐bit wide. All classed in System V as ‘volatile’. Originally each register usable as four 32‐bit floats. 70 new instructions.
SSE increments
At the time of Sandy Bridge based processors (of which there were many), 2011, 64 bit chips.
AVX
From ‘Advanced Vector Extensions’. Appeared in last Pentiums, 2011, 64‐bit chips. Renamed and widened the SSE registers,
ymm0
...
ymm15
256‐bit wide. My guess only, System V volatile. But this time, extra instructions could handle different size data in the registers, and do math on data in the same register.
There was also an AVX2, but onto,
AVX‐512
Originally Knights Landing, and mainstream Skylake, both 2016, 64‐bit chips. Renamed and widened the SSE registers, 32 registers,
zmm0
...
zmm31
512‐bit wide. Guess System V volatile. New instructions.
What does this all mean?
Your assembler must support the different instructions
I talk mostly about NASM, and NASM can. So can most well‐known (actively‐supported) assemblers
(as far as I know) The original floating point registers are physically separate from the XMM registers
So both can be used. Other descriptions of registers, they’re expansions on the existing register set. You need to know widths and instruction sets available on chosen machines. Then use one system or another, as they don’t mix well
…otherwise, it’s the instructions that matter
The original stack operation made sense, but became awkward when it came through that what people wanted was to stream and transform media. You’ll need to look at the appropriate instructions (for example, SSE2 has instructions that allow putting 64‐bit operations into SSE registers)
Are the old registers still there?
Far as I know, yes, and should be available on any post‐386 x86 machine. But they’re not popular—‐I’ve never seen them referred to.
Also, there seems to have been some uncertainty about naming. From the NASM manual,
NASM uses different names to refer to floating‐point registers from MASM: where MASM would call them ST(0), ST(1) and so on, and a86 would call them simply 0, 1 and so on, NASM chooses to call them st0, st1 etc.
References
Some basics on the chips history,
Short history of EAX, very readable,
Wikipedia on the widespread IEEE 754 standard,