18.1 Creating a Simple Fixed-Point Class

I l @ ve RuBoard

In this section we define a fixed-point number class. Unlike floating-point numbers where the decimal point can move from place to place (0.1, 30.34, 0.0008) fixed-point numbers have a set number of digits after the decimal point.

Fixed-point numbers are very useful in applications in which speed is essential but you don't need a lot of accuracy. For example, I've used fixed-point functions for color computations in the printing of color pictures. The logic had to decide which color to select for each pixel. For example, the logic had to determine whether or not to put a red dot on the paper. If the color value was more than half red, a dot was printed. So 0.95 was red and 0.23 was not. We didn't need the extra precision of floating point because we didn't care about the extra decimal places. (0.95 was red. 0.9500034 was still red.)

Since floating-point operations cost a lot more than the integer calculations used to implement fixed point, our use of fixed point sped up the processing considerably.

18.1.1 Fixed Point Basics

In our fixed-point class, all numbers have two digits after the decimal point. No more, no less. That's because we've fixed our decimal point at the second position. Here are some examples of the types of numbers we are dealing with:

 12.34  0.01 5.00 853.82 68.10 

Internally the numbers are stored as a long int . Table 18-1 shows the external representation of some numbers and their internal form.

Table 18-1. External and internal number formats

External representation

Internal representation

12.34

1234

0.00

0.01

1

68.10

6810

To add two fixed-point numbers together, just add their values:

 

External representation

Internal representation

 

12.34

1234

+

56.78

5678

=

69.12

6192

As you can see, addition is a simple, straightforward process. Also, since we are using integers instead of floating-point numbers, it is a fast process. For subtraction, the values are just subtracted.

Multiplication is a little tricker. It requires that you multiple by the values and then divide by a correction factor. (The correction factor is 10 digits , where digits is the number of digits after the decimal point.) For example:

 

External representation

Internal representation

 

1.01

101

*

20.0

2000

Before correction

-- --

202000

= (corrected)

20.20

2020

Division is accomplished in a similar manner, but the correction is multiplied, not divided.

18.1.2 Creating the fixed_pt Class

Externally, our fixed-point number looks much like a floating-point number but the decimal point is always in the same place. Internally we store the number as a long int , so the definition for our fixed_pt class begins with a declaration of the internal data:

 namespace fixed_pt { class fixed_pt {     private:         long int value; // Value of our fixed point number 

Next we define several member functions. These include the usual constructors and destructors:

 public:      // Default constructor, zero everything      fixed_pt(  ): value(0) { }      // Copy constructor      fixed_pt(const fixed_pt& other_fixed_pt) :          value(other_fixed_pt.value)      { }      // Destructor does nothing      ~fixed_pt(  ) {} 

Now we define a conversion constructor that lets you initialize a fixed-point number using a double :

 // Construct a fixed_pt out of a double fixed_pt(const double init_real) :     value(static_cast<long int>(          init_real * static_cast<double>(fixed_exp))) {} 

It should be noted that all we are really doing is setting the value to init_real * fixed_exp , but we want to do our calculations in floating point so we must cast fixed_exp do a double . Also, the result is a long int , so we must add another cast to change the result into the right type. So when all the details are added, a simple statement becomes a little messy.

Notice that we did not declare the constructor as an explicit constructor. That means that it can be used in implied conversions. So the following two statements are both valid:

 fixed_pt::fixed_pt p1(1.23); // Explict does not matter fixed_pt::fixed_pt = 4.56;   // Constructor must not be explicit 

If we declared the constructor explicit , the implied conversion of 4.56 to a fixed-point number would not be allowed by the compiler.

We've decided to truncate floating-point numbers when converting them to fixed point. For example, 4.563 becomes fixed-point 4.56. Also 8.999 becomes 8.99. But there is a problem: the floating-point number 1.23 becomes fixed-point 1.22.

How can this happen? The conversion is obvious. It may be obvious to you and me, but not to the computer. The internal format used to store most floating-point numbers does not have an exact way of storing 1.23. Instead, the computer approximates the number as best it can. The result is that the number stored is 1.229999999999999982. This truncates to 1.22.

So how do we get around this problem? The answer is to fudge things a little and add in a fudge factor to help make the answer come out the way we want it to.

So our full constructor is:

 // Construct a fixed_pt out of a double fixed_pt(const double init_real) :     value(static_cast<long int>(          init_real * static_cast<double>(fixed_exp) +          fudge_factor)) {} 

Actually, as we were writing the code for our fixed-point class, we noticed that we convert a double to a fixed-point number a lot of times. So to avoid redundant code, we moved the conversion from the constructor into its own function. Thus, the final version of the conversion constructor (and support routine) looks like:

 private:     static long int double_to_fp(const double the_double) {         return (             static_cast<long int>(                 the_double *                 static_cast<double>(fixed_exp) +                 fixed_fudge_factor));     } public:     // Construct a fixed_pt out of a double     fixed_pt(const double init_real) :         value(double_to_fp(init_real))     {} 

Finally we add some simple access functions to get and set the value of the number.

 // Function to set the number void set(const double real) {     value = double_to_fp(real);              } // Function to return the value double get(  ) const {     return (static_cast<double>(value) / fixed_exp); } 

As you may recall, the const appearing after the get function was discussed in Chapter 14.

Now we want to use our fixed numbers. Declaring variables is simple. Even initializing them with numbers such as 3.45 is easy:

 fixed_pt::fixed_pt start;    // Starting point for the graph fixed_pt::fixed_pt end(3.45);    // Ending point 

But what happens when we want to add two fixed numbers? We need to define a function to do it:

 namespace fixed_pt { // Version 1 of the fixed point add function inline fixed_pt add(const fixed_pt& oper1, const fixed_pt& oper2)  {     fixed_pt result.value = oper1.value + oper2.value;     return (result); } 

A few things should be noted about this function. First, we defined it to take two fixed-point numbers and return a fixed-point number. That way we group additions:

 // Add three fixed point numbers answer = add(first, add(second, third)); 

Constant reference parameters are used ( const fixed_pt& ) for our two arguments. This is one of the most efficient ways of passing structures into a function. Finally, because it is such a small function, we've defined it as an inline function for efficiency.

In our add function, we explicitly declare a result and return it. We can do both in one step:

 // Version 2 of the fixed_pt add function inline fixed_pt add(const fixed_pt& oper1, const fixed_pt& oper2)  {     return (fixed_pt(oper1.value + oper2.value); } 

Although it is a little harder to understand, it is more efficient.

There's some bookkeeping required to support this function. First, we need a constructor that will create a fixed-point number from an integer. Since this is not used outside of functions that deal with the internals of the fixed-point class, we declare it private:

 private:     // Used for internal conversions for our friends     fixed_pt(const long int i_value) : value(i_value){} 

Since our add function needs access to this constructor, we must declare the function as a friend to our fixed-point class.

 friend fixed_pt add(const fixed_pt& oper1, const fixed_pt& oper2) 

It is important to understand what C++ does behind your back. Even such a simple statement as:

 answer = add(first, second); 

calls a constructor, an assignment operator, and a destructor ”all in that little piece of code.

In version 1 of the add function, we explicitly allocated a variable for the result. In version 2, C++ automatically creates a temporary variable for the result. This variable has no name and doesn't really exist outside the return statement.

Creating the temporary variable causes the constructor to be called. The temporary variable is then assigned to answer ; thus we have a call to the assignment function. After the assignment, C++ no longer has any use for the temporary variable and throws it away by calling the destructor.

I l @ ve RuBoard


Practical C++ Programming
Practical C Programming, 3rd Edition
ISBN: 1565923065
EAN: 2147483647
Year: 2003
Pages: 364

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net