- Immutability: an instance of a Fraction's numerator and denominator should not change once the Fraction is instantiated. This will prevent bugs caused in a situation where two objects have a reference to the same instance, then one object changes its value (mutates it) while the other one expects the value to be the same as it was before.
- Generically Operable: we should be able to use operators: add, subtract, multiply, and divide, in a dynamic fashion with a variable number of fractions. This implementation accomplishes this by taking in an array of fractions for each operation, then recursively solving the first two items of the array until everything has been computed..
- Simplest Form: a Fraction's numerator and denominator should always be as close to zero as possible while remaining integers. This implementation accomplishes this by dividing the numerator and denominator by their greatest common multiple.
The sections below outline the approach I've taken, but I encourage you to go through the code examples to get the full benefit, as I do not describe each detail line by line. Note that there are many libraries available which handle fractions and their operations; this article is mainly to outline a possible approach and consider the pros and cons.
- Accessors/Getters: get the numerator, denominator, and other basic properties of the fraction.
- Operators: perform some operation on a fraction, such as add or divide. Each operation will return a new iFraction and will not modify the original fraction.
- Comparisons: expose methods in which we can check if two fractions are equal or get a numerical comparison (for sorting purposes).
We will now implement the class given the interface above. The class implementation has a few static methods which are useful for facilitating the implementation of the classes, as well as providing some short-hand fractions, such as ONE and ZERO. There are a few private methods, such as operate which facilitate operation calculations by encapsulating common logic so that we do not need to duplicate our code everywhere. We will also be taking advantage of certain mathematical equvialent operations to facilitate code brevity and precision. If the implementation needs to be highly performant, improvements can be made; this implementation serves as a good example / starting base.
The code below outlines some test cases. We are not using any test runner, but instead have created a simple procedure for testing and outputting to the console for convenience, as this code can be copied and pasted directly into any script / project runner. These test cases are not conclusive and do not test all entry points of the code.