CSc 116 notes

Previous page Back    Next Next page   Contents

10) Floating point operations (3.14159)

So far we have only considered numeric operations with integers. Sometimes we want to represent numbers with fractional parts, or very big or very small numbers. In addition, physical measurements cannot be totally precise, rather they can only be made to a certain degree of accuracy. For example, a book published in 1956 states that a river is 120,000,000 years old. It makes little sense to say that today that river is 120,000,049 years old! (The situation is quite different for a bank, it must keep an accurate total of its deposits, which might be $120,000,049.98)

Very big and very small numbers can be expressed more conveniently in "scientific" notation, using some significant digits multiplied by a power of 10, for instance

       1.2×108     0.123456×10-27     -30.09×1030  

Digital computers can store numbers of this sort using the binary equivalent of scientific notation, that is, a sign bit, a sequence of significant binary digits (called the mantissa), and the location of the "binary point" relative to these, in the form of a power of 2 (called the exponent ). These are called floating point numbers, because the point "floats" around in (and beyond) the digits. Historically, many different schemes for storing these 3 quantities have been used. More recently, an IEEE standard has been published for 32 and 64 bit floating point numbers. (As well as some other sizes.) Hardware manufacturers now tend to make floating point arithmetic units to operate on numbers stored according to this standard. In any case, if we are provided with a set of instructions for performing the operations, the details of the storage scheme need not concern us.

The important points to remember are:

The IEEE standard for 32-bit numbers uses 1 bit for sign, 8 bits for expenent (where the binary point is), and 23 bits for significant figures. The leftmost (24th) bit is always one, and hence not stored. For details see wikkipedia

MIPS floating point operations

Like most processors of its time, MIPS is designed to accomodate one or more coprocessors, other chips that share the processing load. Coprocessor 1 will usually be a floating point coprocessor, and this is what SPIM simulates.

There are 2 sizes of floating point numbers:

The coprocessor contains 16 FP registers, named $f0 - $f15. Each FP register can hold either a single or a double.

Input and output:

SPIM provides system services for floating point I/O:

Examples of usage can be found in the program circle.a

Load and store

Data (normally floating) can be moved between memory and FRegisters. Floating constants can be declared in the data segment with the .float directive.

These are pseudo-instructions. The data actually travels through the processor.
For example:

--------------
pi:   .float  3.14159             # assemble a single float in data segment

Floating point arithmetic operations

The expected operations are provided

The pattern is familiar, 3 registers (this time in the coprocessor): destination := source1 (op) source2

In addition, these 2-operand operations are available:

Conversion: Integer <---> Floating

No hardware exists t do arithmetic on a mixture of numeric types. If you want to add an integer to a float, for example, one of them must be converted to the other's type. Likewise single <--> double conversion must be done to match types.

Type conversions are the responsibility of the Floating point coprocessor, and are done with data in the F-registers.The instructions are of the form

which means: convert to single floating, the integer word in FRsource, storing result in FRdest. Note the right-to-left pattern.

Converting integer to floating transforms the bit pattern used, but preserves the numeric value of small integers (up to about 6 digits). Converting from floating to integer, on the other hand, causes any fractional part to be lost (truncated), and if the floating number has a very large magnitude, perhaps its value cannot be stored as an integer.

In this example, we convert pi = 3.14159 to an integer, with the value 3:

Moves to and from the coprocessor

Since conversions are done by the coprocessor, it is necessary to move integer data back and forth between processor general registers and coprocessor registers. This movement does not change the bit patterns, so the data must be converted before being used for operations. The programmer must keep track of the type of data in each coprocessor register. It ia also possible to have single floating data in the general registers, but not very useful.

The instructions for moving data between general registers and coprocessor registers are:

The instruction set allows for multiple coprocessors, for now we are only using coprocessor one, the floating point coprocessor. Note that the general register is always listed first, just like in load and store instructions. Generally, we should be moving integer data.

Example: continuing from above, to calculate the radius of the circle (diameter/2), from the diameter in $f0:

Compare (and branch) with floating numbers

We can compare floating values, and branch depending upon the result. The compares are done by the coprocessor, which generates a "status" of true or false. Then the special branch instructions, "branch on coprocessor condition flag" are used to effect the branch.

The compare instructions have the form   c. condition.s  FR, FR   with 3 of the expected 6 conditions implemented. Instead of .s you may also use .d   The 3 forms are:

Each compare instruction compares the two operands and then sets the coprocessor status, which is then tested by the branch instructions:

bc1t  label  branch if coprocessor 1 condition flag true
bc1f  label  branch if coprocessor 1 condition flag false

Example:

# IF x > y THEN k = 1 ELSE k = 2    x in $f1, y in $f2
c.lt.s $f2, $f1 # y < x is same as x > y
bc1f else
li $t0,1 #THEN k = 1 (in $t0)
b endif
else:
li $t0,2 #ELSE k = 2
endif:

All the possible comparisons can be obtained either by reversing the operands, as we did here, or choosing the opposite branch instruction.

A final word of caution about equality

Thats all you need to know to program floating point operations in (this) assembly language. However, let us remember that floating point calculations are not exact, and therefore it is not a really good idea to expect exact equality of two different computations, even if they should be theoretically equal. This caution holds regardless of the computer language you are using. For instance, adding 1/10 to itself 10 times may not be exactly equal to 1.0! It is more likely to be equal if the additions are done in double, and the result is converted to single for comparing, but even this is not foolproof.

(x == 1.0) is more safely written as abs(x-1.0) < 0.000001

Double precision

64-bit double precision is also supported. The format uses 1 bit for sign, 11 for exponent, and 52 for mantissa (significant bits),
this allows an accuracy of about 15 significant digits.

The important thing to note about MIPS (or at least SPIM) is that there are only 8 64-bit registers, and you refer to them with even numbers: $f0, $f2, $f4...$f14.  $f1 will give a runtime error. Apparently, the double $f0 occupies the space of  $f0 and $f1, etc.

Other than that, use is straightforward. Use .d instead of .s  , the services are 3 for printing $f12, and 7 for reading into $f0.
See for (somewhat complicated) example polishfd.asm .

Prepared by Lin Jensen, Bishop's University, 24 March 1999, revised 20 March 2002
Copyright © 2002 by Lin Jensen. Permission is granted to make exact copies for any non-remunerative use.

Previous page Back   Next  Next page        Contents