Fraction Class
The Fraction Class
Python knows int. Python knows float. It doesn't know frac. You'll teach it!
How? Create a frac class of course. (That's short for "fraction"; and yes, I want you not to capitalize.)
What should that class contain? The Tests below are your best guide. Study them closely. But do let me give you a bit of direction:
What does the frac class need to create a new fraction? Numerator and denominator of course. So in the __init__ method, you'll set two attributes, numer and denom.
If the user tries to create a fraction with a numer and denom that aren't ints, throw an error. Here's a cool line of code that will do that: assert isinstance(numer, int) and assert isinstance(denom, int). In the assert statement, the assert must be followed by a Boolean expression. If that Boolean is True, the line does nothing and execution skips to the next line. But if the Boolean is False, the code ceases execution and exits with an error message.
You should automatically reduce your fractions when they're created. That means you'll need a helper function to reduce. That's Greatest Common Factor (which you wrote in a previous chapter); to reduce a fraction means to divide (// division of course) top and bottom by their GCF.
If you send only one int to __init__ , assume that it's the numerator and that the denominator is 1.
I suggest you store a negative fraction as a negative numerator over a positive denominator. I did.
If __init__ is sent two negative integers, set both numerator and denominator as positive.
You'll overload various built-in operators - + for addition, - for subtraction, * for multiplication, / for division, // for floor division (which always yields an integer, % for modulus (i.e., remainder), ** for exponentiation, == for equality comparison, != for inequality, and > for greater than, < for less than, >= for greater than or equal to and <= for less than or equal to. Remember that to do that you'll need the double-underscores functions. Here's the complete list of those: __init__, __repr__, __lt__, __le__, __eq__, __ne__, __gt__, __ge__, __add__, __radd__, __sub__, __rsub__, __mul__, __rmul__, __truediv__, __rtruediv__, __pow__, __mod__, __rmod__, __rfloordiv__, __neg__, __abs__, __int__, __float__.
When you perform an operation on a frac or a pair of fracs, do not mutate! Of course you could; that is, when you, for example, add you could change the numer and/or denom of one of the fracs. But don't! Instead create a new frac and return that. Here's how to make sure you don't mutate: never have a line of code that beings a_frac.numer = or a_frac.denom = outside the __init__ method.
Your code should handle cases in which an int or a float is on either the left or the right. For instance, it should handle both frac + int and int + frac. Investigate __radd__, __rsub__, __rmul__ , and __rtruediv__.
To add or subtract, you'll need to find a common denominator. You can use Least Common Multiple for this. (You can but needn't. All you really need is a common multiple; and one sure way to get that is to multiply denominators. But that's not elegant! It is however typically faster.) Your wrote LCM in a previous chapter.
m // n, i.e. floor division, can be defined as the nearest int below m / n.
m % n, i.e. remainder, can be defined as: m - (m // n) * n.
A fraction raised to a integer power should return a fraction. A fraction raised to a non-integer power should raise an exception.
Two fractions are equal if their cross-products are equal. You can also define less than and greater than in terms of cross-products. I'll let you figure out how precisely to do that.
If a frac and a float are combined, a float should be returned.
You should implement both int and float type conversion; the dunder (i.e. double -underscore) methods __int__ and __float__ should be used. Int type conversion should yield the largest integer less than the given frac, and float type conversion should simply divide numerator by denominator.
Of your functions, only one will return a string. That's __repr__ of course. The rest will return numbers. Indeed most will return fracs. (Yes, that means that in most of your functions, you'll build and then return fracs.)
A final note about the project structure. It will consists of two files, one named "frac.py" and the other (probably) "main.py". Put the frac class in "frac.py"; the first line of code in "main.py" should be import frac.
Fraction Tests
import frac
print('A Few Fractions')
f1 = frac.frac(12, 15) # Reduce to 4/5
f2 = frac.frac(27, 8) # Irreducible
f3 = frac.frac(5, -2) # Neg fraction is always neg numer over pos denom
f4 = frac.frac(-5, -2) # Represent as 5/2
f5 = frac.frac(42) # Represent as 42/1
f6 = frac.frac(0) # Represent as 0/1
print(f1, f2, f3, f4, f5, f6)
print(type(f1), type(f5)) # Should be type frac.
print()
print("Negation and Absolute Value")
print(-f1, --f1)
print(abs(f1), abs(f3))
print()
print('Two-Frac Operations')
f0 = frac.frac(1)
f1 = frac.frac(1, 2)
f2 = frac.frac(-2, 3)
print(f1, '+', f2, '=', f1 + f2)
print(f1, '-', f2, '=', f1 - f2)
print(f1, '*', f2, '=', f1 * f2)
print(f1, '/', f2, '=', f1 / f2)
print(1, '//', f1, '=', 1 // f1)
print(f2, '%', f1, '=', f2 % f1)
print(f1, '** 3 =', f1 ** 3)
print(f1, '** -3 = ', f1 ** -3)
f2 = -f2
print()
print('Chained Operations')
f3 = frac.frac(5, 4)
print(f1, '+', f2, '*', f3, '=', f1 + f2 * f3)
print(f3, '/', f1, '-', f3, '=', f3 / f1 - f3)
print()
print('Operations with Ints and Floats')
# handle a non-frac on the left and on the right
# if an operand is a float, the output is a float
print('3 + ', f2, '=', 3 + f2)
print('3.0 + ', f2, '=', 3.0 + f2)
print(f2, '+ 3 =', f2 + 3)
print(f2, '+ 3.0 =', f2 + 3.0)
print('3 - ', f2, '=', 3 - f2)
print('3.0 - ', f2, '=', 3.0 - f2)
print(f2, '- 3 =', f2 - 3)
print(f2, '- 3.0 =', f2 - 3.0)
print('3 *', f2, '=', 3 * f2)
print('3.0 *', f2, '=', 3.0 * f2)
print(f2, '* 3 =', f2 * 3)
print(f2, '* 3.0 =', f2 * 3.0)
print('3 /', f2, '=', 3 / f2)
print('3.0 /', f2, '=', 3.0 / f2)
print(f2, '/ 3 =', f2 / 3)
print(f2, '/ 3.0 =', f2 / 3.0)
print('3 //', f1, '=', 3 // f1)
print('3 %', f3, '=', 3 % f3)
print()
print('Comparison')
f1 = frac.frac(2, 3)
f2 = frac.frac(3, 4)
print(f1 == f1, f1 != f1, f1 != f2)
print(f1 == 1, 1 == f1, f1 != 1, 1 != f1, f2 == 0.75, 0.75 == f2, f2 != 0.75, 0.75 != f2)
print(f1 < f2, f2 < f1, f1 < 1, f1 < 1.0, f1 <= f1, f2 <= f1, f2 <= 0.75, f2 <= 0.7)
print(0.7 < f1, 0.7 <= f2, 0 < f1, 1 <= f2)
print()
print("Type Conversion")
f1 = frac.frac(2, 3)
f2 = frac.frac(15, 4)
print(float(f1), float(f2))
print(int(f1), int(f2))
print()
print("Nasty fracs")
f1 = frac.frac(127, 2985)
f2 = frac.frac(3981, 371)
print(f1 ** 2 + f2)
f3 = frac.frac(1, 2)
f4 = frac.frac(2, 3)
f5 = frac.frac(3, 4)
print((f3 ** 2 + f4 ** 3) / (f5 - 5))
f6 = frac.frac(510510, 44100)
f7 = frac.frac(6636630, 573302)
print(f6 == f7, f6 < f7, f6 > f7, f6 - f7, float(f6 - f7))
Output:
A Few Fractions
4/5 27/8 -5/2 5/2 42 0
<class 'frac.frac'> <class 'frac.frac'>
Negation and Absolute Value
-4/5 4/5
4/5 5/2
Two-Frac Operations
1/2 + -2/3 = -1/6
1/2 - -2/3 = 7/6
1/2 * -2/3 = -1/3
1/2 / -2/3 = -3/4
1 // 1/2 = 2
-2/3 % 1/2 = 1/3
1/2 ** 3 = 1/8
1/2 ** -3 = 8
Chained Operations
1/2 + 2/3 * 5/4 = 4/3
5/4 / 1/2 - 5/4 = 5/4
Operations with Ints and Floats
3 + 2/3 = 11/3
3.0 + 2/3 = 3.6666666666666665
2/3 + 3 = 11/3
2/3 + 3.0 = 3.6666666666666665
3 - 2/3 = 7/3
3.0 - 2/3 = 2.3333333333333335
2/3 - 3 = -7/3
2/3 - 3.0 = -2.3333333333333335
3 * 2/3 = 2
3.0 * 2/3 = 2.0
2/3 * 3 = 2
2/3 * 3.0 = 2.0
3 / 2/3 = 9/2
3.0 / 2/3 = 4.5
2/3 / 3 = 2/9
2/3 / 3.0 = 0.2222222222222222
3 // 1/2 = 6
3 % 5/4 = 1/2
Comparison
True False True
False False True True True True False False
True False True True True False True False
False True True False
Type Conversion
0.6666666666666666 3.75
0 3
Nasty fracs
35477589584/3305693475
-59/459
False False True 2431/60196710 4.038426684780613e-05