EXPLORATION 47

image

Project 2: Fixed-Point Numbers

Your task for Project 2 is to implement a simple fixed-point number class. The class represents fixed-point numbers using an integer type. The number of places after the decimal point is a fixed constant, four. For example, represent the number 3.1415 as the integer 31415 and 3.14 as 31400. You must overload the arithmetic, comparison, and I/O operators to maintain the fixed-point fiction.

Name the class fixed. It should have the following public members:

value_type

A typedef for the underlying integer type, such as int or long. By using the value_type typedef throughout the fixed class, you can easily switch between int and long by changing only the declaration of value_type.

places

A static const int equal to 4, or the number of places after the decimal point. By using a named constant instead of hard-coding the value 4, you can easily change the value to 2 or something else in the future.

places10

A static const int equal to 10places, or the scale factor for the fixed-point values. Divide the internal integer by places10 to obtain the true value. Multiply a number by places10 to scale it to an integer that the fixed object stores internally.

fixed( )

Default constructor.

fixed(value_type integer, value_type fraction)

A constructor to make a fixed-point value from an integer part and a fractional part. For example, to construct the fixed-point value 10.0020, use fixed{10, 20}.

Throw std::invalid_argument if fraction < 0. If fraction >= places10, then the constructor should discard digits to the right, rounding off the result. For example, fixed{3, 14159} == fixed{3, 1416} and fixed{31, 415926} == fixed{31, 4159}.

fixed(double val)

A constructor to make a fixed-point value from a floating-point number. Round off the fraction and discard excess digits. Thus, fixed{12.3456789} == fixed{12, 3456789} == fixed{12, 3457}.

Implement the arithmetic operators, arithmetic assignment operators, comparison operators, and I/O operators. Don’t concern yourself with overflow. Do your best to check for errors when reading fixed-point numbers. Be sure to handle integers without decimal points (42) and values with too many decimal points (3.14159).

Implement a member function to convert the fixed-point value to std::string.

to_string( )

Convert the value to a string representation; e.g., 3.1416 becomes "3.1416" and -21 becomes "-21.0000".

To convert to an integer means discarding information. To make it abundantly clear to the user, call the function round(), to emphasize that the fixed-point value must be rounded off to become an integer.

round( )

Round off to the nearest integer. If the fractional part is exactly 5000, round to the nearest even integer (banker’s rounding). Be sure to handle negative and positive numbers.

Other useful member functions give you access to the raw value (good for debugging, implementing additional operations, etc.) or the parts of the fixed-point value: the integer part and the fractional part.

integer( )

Return just the integer part, without the fractional part.

fraction( )

Return just the fraction part, without the integer part. The fraction part is always in the range [0, places10].

Be sure to write a header file (fixed.hpp) with #include guards. Write a separate implementation file (fixed.cpp). Decide which member functions should be inline (if any), and be sure to define all inline functions in fixed.hpp, not fixed.cpp. After you finish, review your solution carefully and run some tests, comparing your results to mine, which you can download from the book’s web site.

If you need help testing your code, try linking your fixed.cpp file with the test program in Listing 47-1. The test program makes use of the test and test_equal functions, declared in test.hpp. The details are beyond the scope of this book. Just call test with a Boolean argument. If the argument is true, the test passed. Otherwise, the test failed, and test prints a message. The test_equal function takes two arguments and prints a message if they are not equal. Thus, if the program produces no output, all tests passed.

Listing 47-1.  Testing the fixed Class

#include <iostream>
#include <sstream>
#include <stdexcept>
 
#include "test.hpp"
#include "fixed.hpp"
 
int main()
{
  fixed f1{};
  test_equal(f1.value(), 0);
  fixed f2{1};
  test_equal(f2.value(), 10000);
  fixed f3{3, 14162};
  test_equal(f3.value(), 31416);
  fixed f4{2, 14159265};
  test_equal(f4.value(), 21416);
  test_equal(f2 + f4, f1 + f3);
  test(f2 + f4 <= f1 + f3);
  test(f2 + f4 >= f1 + f3);
  test(f1 < f2);
  test(f1 <= f2);
  test(f1 != f2);
  test(f2 > f1);
  test(f2 >= f1);
  test(f2 != f1);
 
  test_equal(f2 + f4, f3 - f1);
  test_equal(f2 * f3, f3);
  test_equal(f3 / f2, f3);
  f4 += f2;
  test_equal(f3, f4);
  f4 -= f1;
  test_equal(f3, f4);
  f4 *= f2;
  test_equal(f3, f4);
  f4 /= f2;
  test_equal(f3, f4);
 
  test_equal(-f4, f1 - f4);
  test_equal(-(-f4), f4);
  --f4;
  test_equal(f4 + 1, f3);
  f4--;
  test_equal(f4 + 2, f3);
  ++f4;
  test_equal(f4 + 1, f3);
  f4++;
  test_equal(f4, f3);
  ++f3;
  test_equal(++f4, f3);
  test_equal(f4--, f3);
  test_equal(f4++, --f3);
  test_equal(--f4, f3);
 
  test_equal(f4 / f3, f2);
  test_equal(f4 - f3, f1);
 
  test_equal(f4.to_string(), "3.1416");
  test_equal(f4.integer(), 3);
  f4 += fixed{0,4584};
  test_equal(f4, 3.6);
  test_equal(f4.integer(), 3);
  test_equal(f4.round(), 4);
 
  test_equal(f3.integer(), 3);
  test_equal((-f3).integer(), -3);
  test_equal(f3.fraction(), 1416);
  test_equal((-f3).fraction(), 1416);
 
  test_equal(fixed{7,4999}.round(), 7);
  test_equal(fixed{7,5000}.round(), 8);
  test_equal(fixed{7,5001}.round(), 8);
  test_equal(fixed{7,4999}.round(), 7);
  test_equal(fixed{8,5000}.round(), 8);
  test_equal(fixed{8,5001}.round(), 9);
 
  test_equal(fixed{123,2345500}, fixed(123,2346));
  test_equal(fixed{123,2345501}, fixed(123,2346));
  test_equal(fixed{123,2345499}, fixed(123,2345));
  test_equal(fixed{123,2346500}, fixed(123,2346));
  test_equal(fixed{123,2346501}, fixed(123,2347));
  test_equal(fixed{123,2346499}, fixed(123,2346));
  test_equal(fixed{123,2346400}, fixed(123,2346));
  test_equal(fixed{123,2346600}, fixed(123,2347));
 
  test_equal(fixed{-7,4999}.round(), -7);
  test_equal(fixed{-7,5000}.round(), -8);
  test_equal(fixed{-7,5001}.round(), -8);
  test_equal(fixed{-7,4999}.round(), -7);
  test_equal(fixed{-8,5000}.round(), -8);
  test_equal(fixed{-8,5001}.round(), -9);
 
  test_equal(fixed{-3.14159265}.value(), -31416);
  test_equal(fixed{123,456789}.value(), 1234568);
  test_equal(fixed{123,4}.value(), 1230004);
  test_equal(fixed{-10,1111}.value(), -101111);
 
  std::ostringstream out{};
  out << f3 << " 3.14159265 " << fixed(-10,12) << " 3 421.4 end";
  fixed f5{};
  std::istringstream in{out.str()};
  test(in >> f5);
  test_equal(f5, f3);
  test(in >> f5);
  test_equal(f5, f3);
  test(in >> f5);
  test_equal(f5.value(), -100012);
  test(in >> f5);
  test_equal(f5.value(), 30000);
  test(in >> f5);
  test_equal(f5.value(), 4214000);
  test(not (in >> f5));
 
  test_equal(fixed{31.4159265}, fixed{31, 4159});
  test_equal(fixed{31.41595}, fixed{31, 4160});
 
  bool okay{false};
  try {
    fixed f6{1, -1};
  } catch (std::invalid_argument const& ex) {
    okay = true;
  } catch (...) {
  }
  test(okay);
}

If you need a hint, I implemented fixed so that it stores a single integer, with an implicit decimal place places10 positions from the right. Thus, I store the value 1 as 10000. Addition and subtraction are easy. When multiplying or dividing, you have to scale the result. (Even better is to scale the operands prior to multiplication, which avoids some overflow situations, but you have to be careful about not losing precision).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset