This lab will help you practice with classes and libraries.
Design and code a class to represent the set of rational numbers ala mathematics. Recall that rational numbers have a numerator and a denominator both of which come from the set of integers.
Common mathematical operations on rational numbers are addition, subtraction, multiplication, division, and reciprocal:
A B Reciprocal of --- = --- B A A B A + B Sum of --- and --- = ------- C C C A C A*D + C*B Sum of --- and --- = ----------- B D B*D A B A - B Difference of --- and --- = ------- C C C A C A*D - C*B Difference of --- and --- = ----------- B D B*D A C A*C Product of --- and --- = ----- B D B*D A C A D A*D Quotient of --- and --- = --- * --- = ----- B D B C B*C A A -A A Additive Inverse of --- = - --- = ---- = ---- B B B -B Comparison: A C --- < --- --> A*D < C*B B D A C --- ≤ --- --> A*D ≤ C*B B D A C --- > --- --> A*D > C*B B D A C --- ≥ --- --> A*D ≥ C*B B D A C --- = --- --> A*D = C*B B D A C --- ≠ --- --> A*D ≠ C*B B D
Noting that we need not consider special cases such as 'like denominators' for addition and subtraction, we can throw those two formulas out. We also note the use of both multiplication and reciprocal during division and so we will get to re-use our functionality (should we so desire).
To make sure the sum and difference (and maybe the product and quotient?) do not overflow the integer type we choose, we should make sure to keep the numerator and denominator 'reduced to lowest terms' at all times. This means we will need a helper function to find the least common multiple (LCM; aka the greatest common divisor; GCD) of the numerator and denominator. We could make this a private class function/method... But, due to its generic nature — having nothing specifically to do with rational numbers — we should probably place it in its own library (maybe a math related one?).
(Note: You cannot use either the gcd or lcm functions from the standard library! You must code the greatest common divisor for yourself to receive full credit. Note also the option below for a faster method.)
Display and entry of rational numbers can be deceptively difficult so let's keep it as simple as possible and forgo the standard notation used above in favor of the more basic: A/B. (For further discussion, please see the notes elsewhere on the site.)
To keep the display format as readable as possible, we should keep the denominator positive at all times. (So if we read or calculate a negative denominator somewhere, we should take the additive inverse of both it and the numerator — 'canceling' the common -1 factor from both.) Along with keeping the denominator positive, let's keep it non-zero, too. (It wouldn't do to be dividing by 0, now would it?)
Speaking of dividing, provide a method (asdecimal, decimalform, approximate, or whatever) which returns the decimal representation of the rational number (1/2 --> 0.5).
Noting that formulas are provided for unary negation and comparison/testing, you should provide methods for those operations, too.
Don't forget to include a complete set of constructors, accessors, and mutators in addition to the mathematics specialty functions and input/output mentioned above.
Place your rational ADT in a library.
Write a test application (driver) to make sure ALL of your operations work correctly.
Did you choose short or long for your numerator/denominator type? Why is yours the best choice?
How can you keep the denominator positive? What happens to the numerator? Where should this code be placed?
Do you have any private method(s)? If so, what does it/them do? (Hint: *ahem* reduce() *ahem* normalize() *ahem*)
What type of value is returned from your comparison/testing functions? (Hint: In what context are they likely to be used? What [data] type would be appropriate there?)
How many lines are in your less and equal functions? (I bet there aren't many...)
Is there any original code in your methods for greater, greater_equal, less_equal, and/or not_equal? (Hint: Maybe they just re-use some other functions you've already written in a clever way?)
Do your math functions change the calling object or their [single] argument? Should they? (Hint: What about when you code x + y — does the compiler change either x or y?) How do you specify such situations to the compiler so it can better handle/enforce your decision?
How many lines are in your math functions? (Hint: You are constructing a new object to return to the caller...)
Does your input method prompt the user before reading the rational number? Why should it not?
Does your output method print anything besides the rational number (in proper notation, of course) — even an endl? Why should it not?
Why do/can you only have one mutator? Don't we normally have a separate mutator method for each data member of the class? (Hint: Does this relate to the thought-provoking question above? What other things might also influence this decision? What might happen if the programmer could change the numerator but not the denominator? Vice versa? Would this complicate your validation rules?)
This assignment is (Level 5).
Add (Level 2) to properly apply inline'ing and const-ness to ALL of your class methods. (This includes proper use of initializer lists on constructors!)
Should any of your methods be inlined for speed? Which ones will you inline? How does one do this for a class method?
If you were to end up inline'ing all of your methods, would you still need an implementation file for your library? Why/Why not?
Do the accessors, output, or math operation methods alter the calling object in any way? How do you specify such a situation to the compiler so it can better handle/enforce your decision?
What needs to be in your constructor initializer lists? Is there a certain order to the list? Does it go on the prototype or the definition?
Add (Level 2) for using Euclid's algorithm — with full comments! — in your greatest common divisor routine. (Hint: I'll let you find this one on your own, but I will say that I've always found the mathematics portal on Wikipedia to be of great help in situations like these.) (Hint to the hint: It's just three clicks away if you are paying attention...)
Add (Level 2) for overloading your math operation functions to allow mixing of rational numbers with built-in integer types. (Hint: What would be the result of adding 1/2 to 5? While answering, recall that the only data types you have handy are integers and Rational — floating point wouldn't make any sense as it would lose precision in most cases.)
Add (Level 4) to use an arbitrary precision integer class for your numerator and denominator instead of either short or long. Of course, this option assumes that you are coding the Integer class yourself — not just stealing one you've found. (*ahem*)
Add (Level 3) for making your math operations actually overloaded operators. (If you did the built-in mix-in option, you can make those operator overloads for another (Level 1).) (Heck! If you did the Integer class option, you can create a set of overloaded operators for that, too. I'll give you another (Level 1) for that set. *smile*)
If you did all above options, this lab could be worth as much as (Level 20).