Preliminary
I'm a big proponent of learning mathematics. It's one of the biggest and most potent tools in the arsenal of a programmer. Right now though, the mathematics I'm interested in expanding upon is rather simple — or should I say, extremely simple. It's the elementary math of working with fractions.
The relevant ideas are:
- Adding, subtracting, multiplying, and dividing fractions.
- Taking the reciprocal of a fraction.
- Simplifying a fraction to lowest terms.
- Converting between fractions, mixed numbers, and decimals.
In my Math Foundations mini course, I give a quick review of these ideas so if you feel that your knowledge is a bit rusty, feel free to check it out: Math Foundations: Fractions.
Alright, so the point here is to create a Fraction class in the object-oriented programming (OOP) language of your choice. I wanted to design the class's specifications such that it's easily implementable across a wide range of languages and is a good standard to give you a mature, hands-on experience of OOP.
The Fraction class
Let's discuss what the Fraction class is intended to do. You should create a mutable class as the first part of this guide and then for the extra cheese on top, create an immutable version. (Mozzarella cheese I'd say!)
When instantiating a Fraction, you must provide a numerator and denominator. Both must be integers. Also the denominator must not be 0. If either of these conditions fail, raise an exception (which is usually possible in many OOP languages).
During initialization, the fraction must be reduced to lowest terms. For instance, if you do:
new Fraction(2, 10)the fraction should be stored as ::1/5::.
The core operations of this class are:
- The four standard arithmetic operations. Each one should mutate the original fraction. For example, calling
a.add(b)should addbtoaand mutatea(bshould remain as it is). - Reciprocate the fraction.
- Check for proper or improper fractions.
Besides these manipulative oeprations, your Fraction class should also support operations to print the fraction out. There are three kinds here:
- Printing normally.
- Printing as a mixed number.
- Printing as a decimal. For a recurring decimal, the output should make the recurring part clear. Use the general form
a.b(c)for this, where(c)is the recurring part. For example,1.(3)represents the recurring decimal ::1.3333...::.
Here's an example for printing the fraction ::7/4:: in each of these three kinds (in the same order):
7/4 1 3/4 1.75
Here's an example for printing ::-4/3:::
-4/3 -1 1/3 -1.(3)
And this is pretty much it.
Let the thinking commence
Notice that I haven't told you about the names of the methods, their signatures, or anything of that sort. I want you to really think through this task and produce a decent class implementation.
Where possible, leverage OOP principles. For example, if you feel that a property must be hidden from the outside world, make it private. Besides, you're free to (and will actually need to) create utility functions for some operations.
Here are some points to ensure:
- Don't let errors go unnoticed in your code. Prevent them early on.
- Don't let bad input be provided to
Fractionduring instantiation. - Check for any exception cases when performing operations (for example, when the numerator is 0 and you intend to compute the reciprocal).
- Don't try to be perfect in the first run. Be more focused on producing a working implementation and then clean it up by going through it again and again.
If it takes you a lot of time to implement this class, don't worry. I myself took a couple of hours to implement it, even though I have years of experience programming. What really matters is that you learn about good programming practices, abstraction, naming, and how to design an OOP class from scratch.
This isn't you against anyone else but rather you against yourself. Challenge yourself, enjoy coding, and don't forget to think on paper and take breaks. A healthy mind leads to healthy code.
The solution
I don't want to spoon-feed you each and every single detail of creating this Fraction class. In fact, this would be far from ideal because then you won't truly understand the class's implementation yourself.
As I stated earlier, I want you to tackle this challenge yourself. Yes, I could present an extremely detailed walkthrough but you could be reading it for ages but still not able to produce a class on your own — because learning doesn't happen by reading but rather by doing.
I've attempted to create this class in one language at the moment, that is, JavaScript. The entire source code is available on Gitlab and any details relevant to the code are presented there as comments in the code or in the README file.
Solution in JavaScript on Gitlab
Two versions are presented: one a mutable class that mutates the calling Fraction instance, and the other an immutable class that produces a completely new Fraction instance on each operation.
Read the code carefully and compare it with yours.
Bear in mind that my code isn't a "canonical" or a "benchmark" for what you must produce; it's just one of many possible ways to solve the given problem, leveraging good programming practices and OOP principles. Yours could take different approaches in different places and that's fine as long as your code is readable and correct.
Further improvements
There are some things that could be improved in the linked source code and even in the proposed object-oriented design above.
For example, take a minute or two to think that whether including an "output" mechanism directly in the Fraction class is a good idea or that you should rather have something like "converting to string."
Also, notice how the remainder obtained in each step of the while loop in printDecimal() is searched for in the list rems. Is using a list a good idea? Can this be made slightly more efficient?
As I said earlier, now that you hopefully have a working implementation, the next step is to glare at it and try to refine it as much as you can. Improve variable names if possible. Make given algorithms efficient if possible. Just try to refine your first draft. Because that's what all programmers do.