Features of using x86 real-life registers

• Tutorial
In this article, we consider the experience of the author, who was faced with the peculiarities of the implementation of work with real numbers at the hardware level. Many modern IT professionals work with high levels of data abstraction. It seems that the article will open their eyes to some interesting things.

Once upon a time in lectures of PNAVU (Programming in high-level languages) we were told about real numbers. The first information was superficial. I got to know them closer after I finished my studies at the university, and this acquaintance made me think a lot. And this acquaintance happened after we did not fit into the double data type in the calculations.

I got a program written in C ++ using the Borland Turbo C ++ compiler. For calculations, the double data type was used in it, i.e. material type double precision. At certain points in time, this double program overflowed and successfully crashed. The factorial was calculated in the program, and the maximum factorial that can fit in double is 170! ≈ 7.3 306 . The calculation of the factorial 171! ≈1,2 309 caused an overflow of the double data type. It was the problem of overflow that led to the study of the current situation in calculations with real numbers. More on this later in the article.

Overflow of real numbers of double precision is a global problem, which consists of three components: support by the programming language, support by the compiler and the architecture of the processor on which our program will run.

With a programming language, everything is simple and standardized. In our hated beloved C ++ language, there are three real data types: float, double and long double, respectively single, double and more than double precision. Moreover, the standard language says that “the type long double provides at least as much precision as double”. That is, long double must be no less than double. It was this loophole in the standard that the developers of the Borland Turbo C ++ compiler took advantage of, equating the long double to double.

The x86 architecture is also not going smoothly. For the simplest mathematical operations (addition, subtraction, multiplication, division, shifts, calculation of mathematical functions sin, cos, etc.), the developers of the processors provide the corresponding registers. Registers can be conditionally divided into those that work with integer numbers and those that work with real numbers. There are processor architectures in which there are no registers for working with real numbers. For example, ARMv7. In such cases, the time of operations on real numbers increases by several orders of magnitude, since these operations now need to be emulated programmatically using integer registers and the operations of addition, subtraction and shift. Calculation of, for example, trigonometric functions programmatically could slow down the calculations by several orders of magnitude,

Lyrical digression. This is exactly the problem we encountered at one of the projects. It was necessary to count the number of people passing under the camera. Used an embedded solution with ARMv7 for real-time video processing. Recognized and considered past people. And image processing is work with real numbers, which in the used architecture just did not exist. I had to switch to a more advanced hardware solution, but that's another story. Let's go back.

The widely used x86 architecture before the 80486 processor also had no real registers. Old-timers probably remember such a thing as a mathematical coprocessor, which was installed next to a conventional processor and had the corresponding designation (8087, 80287 or 80387) and worked without active cooling and even without a radiator. It was the appearance of the 8087 coprocessor that served as the impetus for the emergence of the IEEE 754-1985 standard, we will reflect on it later.

These coprocessors added three abstract real data types, eight 80-bit registers and a bunch of assembler instructions to work with them. Now, conditionally, in one cycle it was possible to add, subtract, multiply, divide, and also extract the numbers, calculate the trigonometric function, etc. Acceleration of calculations reached 500% on specific tasks. And on the tasks of word processing there was no acceleration, therefore they set this coprocessor for \$ 150 optionally. Few people listened to music on the computer then, and the video wasn’t generally for the general user.

Beginning with the 80486 series processors, the coprocessor began to be integrated into the processor itself. In addition to Intel486SX, this processor came out later and had a disabled coprocessor. Physically, he did not differ much from the rest of the processors. Apparently, Intel decided to implement defective copies with errors in the field of coprocessor.

Let us consider in more detail the real registers of the mathematical coprocessor. Although, in fact, this is a register of the same type. Large, 80-bit, and in the presence of their 8 pieces in a stack. But the programmer has three types of abstraction of real numbers: short (single) format (single precision), long (double precision) and extended format for representing numbers (extended precision). Here the Russian translation of the terms is given from the book [1]. Characteristics of real numbers are presented in the table:

If the programmer chose to use, for example, a short format (32 bits), then the coprocessor inserted the number into the 80-bit register, performed operations on it, and then returned the number back in a reduced size, if during the work it went beyond the short format , then NaN was returned (not a number - not a number).

Further development of x86 architecture added a bunch of extensions (MMX, SSE, SSE2, SSE3, SSSE3, SSE4, SSE5, AVX, AVX2, AVX-512, etc.), and together with extensions new registers 128, 256, 512 bits long [2] , and a bunch of new assembler commands to work with them. These extensions provide the ability to work only with single and double precision real numbers, for example, each 512-bit register is capable of working with either eight 64-bit double-precision numbers or sixteen 32-bit single-precision numbers.

From thinking about architecture, let's move on to compilers. In the C ++ programming language, the float data type corresponds to 32-bit real numbers of x86 architecture, double to 64-bit, but with long double everything is much more interesting. As mentioned above, many compiler developers use the assumption of the standard and make the long double type equal to double. But the hardware x86th allows you to operate with an extended 80-bit format. And there are compilers that let you use them. Let's consider compilers in more detail.

Oddly enough, but among those who ignore the 80-bit extended data presentation format there are many well-known and widely used compilers, here is an incomplete list: Microsoft Visual C ++, C ++ Builder, Watcom C ++, Comeau C / C ++. But the list of compilers supporting the extended format is quite interesting: Intel C ++, GCC, Clang, Oracle Solaris Studio. Let's consider compilers in more detail.

The compiler from Intel could not fail to have an extended format - how will the manufacturer leave its hardware without the appropriate tool? Using the compiler is not free. The compiler is widely used in scientific calculations and in high-performance multiprocessor systems.

The free GCC compiler easily supports the extended format under the Linux operating system. With Windows, everything is more interesting. There are two adaptations of the compiler for Windows: MinGW and Cygwin. Both can manipulate the extended format, but MinGW uses Microsoft runtime, which means that real numbers that exceed the 64-bit double cannot be seen / displayed anywhere. With Cygwin, things are a little better, since porting is more complex.

Clang, like GCC, supports the extended format.

Well, a little about Oracle Solaris Studio, formerly Sun Studio. Toward the end of its existence, Sun made its many technologies available. Including your compiler. It was originally developed for the Solaris OS with SPARC architecture processors. Later, the operating system, along with the compiler, was ported to the x86-th architecture. The compiler along with the IDE is available under the Linux operating system. Unfortunately, this compiler is “thrown up” and does not support the latest C ++ trends.

To solve the double overflow problem, voiced at the beginning of the article, after all thought, suffering and searching, it was decided to completely rewrite the code and use the features of the GCC Cygwin compiler. The long double data type was used to store data. The performance of similar systems using 64-bit and 80-bit real numbers is different. When using 64-bit real numbers, the compiler tries to optimize everything and use the fastest “latest” extensions of x86 architecture. When switching to 80-bit numbers, the “ancient” “coprocessor” part of the architecture is involved.

Of course, it was possible to solve the overflow problem by using the software method for processing large real numbers, but then the performance drop would be significant, since the program calculated mathematical models containing trigonometric functions, extracting the root, and calculating the factorial. The work of calculating the model using the extended format took about 8 - 12 hours of processor time, depending on the input parameters.

At the end of the article, we will reflect on the IEEE 754 standard [3,4,5]. The first version of the standard, as was noted, came out thanks to the 8087 mathematical coprocessor. Subsequent versions of this standard were released in 1997 and 2008. It is the 2008 standard that is most interesting. It describes real numbers of quadruple precision (quadrupole, quadruple-precision floating-point format) [6]. It is this data storage format that would be best suited for the above task. But it is not implemented in the affordable processor architecture of common computers. On the other hand, x86 architecture has long had registers (128, 256, 512 bits) of the required size, but they serve to quickly work with several single and double precision numbers. I met on the Internet information about

Of the modern architectures that support quadruple accuracy, we can distinguish SPARC V8 and V9 architectures. Although they appeared back in 1990 and 1993, respectively, the physical realization of quadruple precision appeared only in 2004. In 2015, IBM released the POWER9 CPU specification (ISA 3.0), which has support for quadruple real numbers.

The accuracy of quadruple real numbers is unnecessary for a wide range of users. It is mainly used in scientific calculations. For example, in astrophysical calculations. This can explain why the IBM360 computers manufactured in the 70-80s had support for 128-bit real numbers, but, of course, that did not comply with the modern IEEE 754. They used this machine mainly in scientific calculations.

Let’s also say a few words about the Russian developer of MCST processors. This company designs and manufactures SPARC architecture processors. But, interestingly, at first they developed and released processors of the “old” architecture of SPARC V8 (MTsST-R150 in 2001 and MTsST R500 in 2004) without the support of real numbers of quadruple precision, although the new architecture of SPARC V9 has long been. And only in 2011 they released the MTsST R1000 processor with SPARC V9 architecture with support for real numbers of quadruple precision.

A few more words about the IEEE 754 standard. There is an interesting article on the Internet [3], which, very emotionally, describes the problems and disadvantages of the existing standard. The article [4] also describes the standard and its problems. In addition, it talks about the need for new approaches in representing real numbers. The two above articles describe many of the shortcomings of the representation of numbers; for my part, I’ll add this. In programming, there is such a term as “crutch”, it is something wrong, but it helps to solve the current problem at the given time not in the most optimal way. So, real numbers corresponding to the IEEE754 standard are a crutch.

And this conclusion appeared here why. Because there is no negative zero, conversion to decimal format and vice versa is ambiguous, when working with real numbers, the programmer should always remember the dangerous behavior of real numbers when approaching the permissible limits of a possible range of values, and when comparing real numbers, you need to compare the range with acceptable accuracy.

Fascinating materials and sources:

1. Yurov V.I. Assembler. Textbook for high schools. 2nd ed. - SPb .: Peter 2005
2. x86
3. Yurovitsky V.M. IEEE754-tika threatens humanity
4. Yashkardin V. IEEE 754 - Binary Floating-Point Arithmetic Standard
5. IEEE 754 Wikipedia Articles: One , Two, and Three .
6. Wikipedia article on quadruple precision of real numbers