Monash University > School of Computer Science and Software Engineering > CSE1303 > Part B > Pracs > Prac B2

CSE1303 Computer Science
Semester 3 (summer), 2003
Part B
Prac B2

This prac covers material from lectures B01 to B06. Many questions in this prac relate to material in Tutorial B2.

Background

For this prac, you will need to be introduced to a new data type in C, the union.

A union is like a struct, except that all of its members occupy exactly the same piece of memory. Naturally, since they share the same bits, only one of the "members" actually has a meaningful value at any given time.

/* This type holds either a float or a long. */

union FloatLongUnion
{
    float f;
    long l;
};

You access a member of a union as you do a struct, using the dot (.) operator.


Normally, such a data type is kept inside a structure, with another member of the structure identifying the type currently being used in the union:

struct FloatLongRec
{
    int type;   /* (say) 1 for float, 2 for long */
    union FloatLongUnion value;   /* either a float or a long */
}

typedef struct FloatLongRec FloatLong;

/* ... later ... */
FloatLong num;
num.type = 1; num.value.f = -0.75;    /* make it a float */
num.type = 2; num.value.l = 42;       /* make it a long */

... but we won't be doing that here, because we only need to use the union for a specific purpose.


If you store a value in one of a union's "members", but read it back using a different "member", then the bit pattern which was stored is interpreted as if it were the type you are reading. Thus if you do this:

union FloatLongUnion pun;
float floatvalue = -0.75;
long bitpattern;

pun.f = floatvalue;  /* Store in union as a float ... */
bitpattern = pun.l;  /* ... but read back as a long */

... then you'll get back a C long which contains the bit pattern for -0.75.

This works best when the two types (in this case, float and long) are the same size (which they are: 4 bytes).

Assessment

For this prac your answers should address the following criteria. Your demonstrator will consider these items when giving your answers a mark.

Preparation (3 marks)


The marks for Preparation will be awarded only if the preparation is completed before the start of the class.

Even if you do not get the marks for preparation, you will need to complete it before going on with the regular questions, because they will refer to its answers.


  1. Write a C function int bit(const long x, const unsigned int n) which returns the value of the nth bit in the binary representation of x. The range of n is 0 (least significant bit) to 31 (most significant bit). This function should return 0 or 1, and should produce no output. Write a short program to test your function. Be sure it works for negative values of x (n = 31).
  2. Consider a union containing the following two members:

    Write a corresponding C union declaration.

    Write a C program which uses this union to determine whether the computer it is running on is big-endian or little-endian.

    Hint: The long occupies four bytes, but the order that those bytes are stored in depends on the machine's byte order. The array of char is the same regardless of the machine's endian-ness. Try putting a value like 0x12345678 in the long and examine the bit pattern through the array.

    Try this program on as many computers as you can. See what you can detect with it.

  3. Using a union containing the following two members:

    write three functions to extract and return the sign, exponent and mantissa from a floating-point value.

Question 1 (2 marks)

This question is about representation of integers. It does not require use of a union.

Write a program which reads a (signed) long from standard input, then prints out the 32-bit binary representation for the number.

Here's a sample (underlined indicates user input):

Type a number: -5
Representation is: 11111111111111111111111111111011

There are no printf format specifiers to output in binary, so you will have to test each bit of the number and print '1' or '0' accordingly.


Here are some format specifiers for printf and scanf:
Typeprintfscanf
int%d%d
long%ld%ld
unsigned int%u%u
unsigned long%lu%lu
float%f%f
double%f (yes, this is the same as for a float) %lf (yes, this is different to printf)

You can get the full set if you're running Unix by reading the functions' manual pages with

man 3 printf
man 3 scanf

Question 2 (2 marks)

This question is about floating-point numbers and representations. You will need to use a union for this question.

Write a program which does the same as Question 1, but for a C float instead. It should identify each component of the floating-point number and state what effect each component has on the number's final value.

Here is some sample output:

Type a number: -0.75
Representation is: 10111111010000000000000000000000
Sign is: 1 (negative)
Exponent is: binary 01111110 = decimal 126 (-1 after subtracting excess)
Mantissa is (including implicit leading 1):
  binary 1.10000000000000000000000 = decimal 1.50000

Your program should identify the special cases infinity and zero by examining the exponent. Your program does not need to handle not-a-numbers (NaN) or denormalized numbers.

Test your program by typing in a large range of numbers, including some in scientific notation (e.g., -1.34e-35). Try to find the largest and smallest values that can be represented as a float.


To find the decimal equivalent of the binary mantissa (1.50000 in the above example), extract the mantissa to a long and add the implicit leading 1, and floating-point-divide it by 1 << 23; print this value, which will be between 1 and 2.


Question 3 (3 marks)

Write a C function float fmul(const float a, const float b) which multiplies two floating-point values and returns its result, without using any floating-point C operations. Using integer operations is acceptable, as is assigning to/from floating-point variables and passing and returning floating-point variables to and from functions.

(In other words, this is not a valid answer:

float fmul(const float a, const float b)
{
    return a * b;
}
)

Write a C program to test your function.

Recall the steps performed in multiplying floating-point numbers:

  1. Determine sign of result. If a and b have same sign, result is positive. Otherwise it is negative.
  2. Determine exponent of result. Add exponents of a and b together. Don't forget to correct the exponents for excess.
  3. Determine mantissa of result. Perform a fixed-point multiplication of the mantissas of a and b. Don't forget to add the implicit leading 1 to the mantissas before multiplying.
  4. Renormalize the result. Adjust exponent so that the mantissa is between 1 and 2.
  5. Pack result into float format. Be sure to remove the implicit leading 1 from the mantissa.

Your program should detect overflow and underflow, and return (plus or minus) infinity or zero accordingly. Do not worry about the slight rounding you'll get in the least significant bit; nor should you worry about nonsensical products like (0 * infinity).


To perform a fixed-point multiplication of two longs x and y, decide where the implicit binary point is; in this prac it will be just to the right of bit 23, meaning there are 23 bits to the right of the point. Call this number p (for "precision").

The multiplication can be done in C with this expression (the casts ensure no loss of precision during the multiplication):

result = (long) (((long long) x * (long long) y) >> p)

The result is expressed in the same fixed-point representation.

If you are using Borland C, long long is spelt __int64, with two leading underscores.


Bonus question (2 bonus marks)

Write a C program which does the same as Question 3 above, except that the function is called fadd and it performs floating-point addition instead. Recall the steps involved in performing an addition:

  1. Determine if effective operation is addition or subtraction. If a and b have same sign, operation is addition. Otherwise, operation is subtraction.
  2. Identify number with smaller magnitude. Compare exponents, then break ties by comparing mantissas.
  3. Determine sign of result. Sign is the same as the larger number (regardless of whether effective operation is addition or subtraction).
  4. Determine exponent of result. Exponent of result is same as exponent of larger number.
  5. Denormalize smaller number. Rewrite its mantissa and exponent so that it matches the larger number.
  6. Add (or subtract) smaller mantissa to (from) larger mantissa to produce the result's mantissa.
  7. Renormalize the result. Adjust exponent so that the mantissa is between 1 and 2.
  8. Pack result into float format. Be sure to remove the implicit leading 1 from the result's mantissa.

Again, try to handle the special cases that produce infinities or zero. Do not worry about rounding or handling silly input like (infinity + -infinity).

[ Top | Home ]

Last modified 2002-07-03