31
A Fraction Class Have you ever used a calculator that had a fraction mode? You can enter fractions and they are NOT converted to decimals – they remain fractions. C# does not have a fraction data type. We will build one. What would a fraction class look like? A fraction class would have to allow us to do all of the things we normally do with fractions: Create new fractions (constructors) Set the numerator and denominator (properties) Print/display fractions (ToString) Convert fractions to their decimal equivalents (ToDouble) Reduce fractions Do arithmetic on fractions Compare fractions We would want to be able to declare fractions in several ways: Fraction f1 = new Fraction(); //gives a value of 0/1 Fraction f2 = new Fraction(5); //gives a value of 5/1 Fraction f3 = new Fraction(1,2); //gives a value of 1/2 We will also want to do things like this: f1 = f2 * f3; It actually is possible to redefine C#'s operators, but we won't get into that this term. Instead, we will do arithmetic operations by writing functions. So we have to do it like this: f1 = f2.Times(f3); Note that this should NOT change f2 or f3. We may also want to do addition, subtraction, and division: f1 = f2.Plus(f3); f1 = f2.Minus(f3); f1 = f2.DividedBy(f3); Some other things we might want to do with fractions: f1.Reduce(); //Tell f1 to reduce itself f1 = f2.Reduced(); //Don't change f2 if (f1.Equals(f2))... Console.WriteLine(f1.ToString()); //Convert to String Console.WriteLine(f1.ToDouble().ToString()); //Convert to Double 3/5/2022 document.docx Page 1 of 31

Properties - Briar Cliff Universityold.briarcliff.edu/.../Notes/Day22--Objects--AFractionClass.docx · Web viewYou can enter fractions and they are ... A fraction class would have

  • Upload
    vancong

  • View
    215

  • Download
    1

Embed Size (px)

Citation preview

A Fraction ClassHave you ever used a calculator that had a fraction mode? You can enter fractions and they are NOT converted to decimals – they remain fractions. C# does not have a fraction data type. We will build one.What would a fraction class look like? A fraction class would have to allow us to do all of the things we normally do with fractions:

Create new fractions (constructors) Set the numerator and denominator (properties) Print/display fractions (ToString) Convert fractions to their decimal equivalents (ToDouble) Reduce fractions Do arithmetic on fractions Compare fractions

We would want to be able to declare fractions in several ways:Fraction f1 = new Fraction(); //gives a value of 0/1Fraction f2 = new Fraction(5); //gives a value of 5/1Fraction f3 = new Fraction(1,2); //gives a value of 1/2

We will also want to do things like this:f1 = f2 * f3;

It actually is possible to redefine C#'s operators, but we won't get into that this term. Instead, we will do arithmetic operations by writing functions. So we have to do it like this:

f1 = f2.Times(f3);

Note that this should NOT change f2 or f3. We may also want to do addition, subtraction, and division:

f1 = f2.Plus(f3);f1 = f2.Minus(f3);f1 = f2.DividedBy(f3);

Some other things we might want to do with fractions:f1.Reduce(); //Tell f1 to reduce itselff1 = f2.Reduced(); //Don't change f2if (f1.Equals(f2))...Console.WriteLine(f1.ToString()); //Convert to StringConsole.WriteLine(f1.ToDouble().ToString()); //Convert to Double

Let's start building our class. Create a new Windows Console project called FractionTest. Then create a new class called Fraction.

5/6/2023 document.docx Page 1 of 23

Properties

A fraction has two properties: the numerator and denominator. We will store these values in local private variables called numerator and denominator.

private int numerator, denominator;

Testing our Code

Go to the main method and write the following: Fraction f1;

This should compile and run, but it doesn't do anything. Let's try to print it out by adding the following:

Console.WriteLine("f1" + f1.ToString());

This gives us a "Use of unassigned local variable f1" error message. This means that we have not yet created a Fraction object; we have only declared it. Whenever you declare a new object variable (like f1), you must also allocate memory for it using the new keyword, like this:

Fraction f1 = new Fraction();

Now run the program. What we get is: f1: FractionTest.Fraction

The ToString method returns a string to print, but it's not you probably expected. Instead, it prints out the type (class) of the variable. Every object in C# has a built-in ToString method that simply prints out the object's type (class). Why does it do this? The creators of C# anticipated that every time you create a class, you are probably going to want to print out the values of the objects in that class, so they provided a default ToString method. However, the creators of C# could not possibly know exactly what we would want to print out, so they decided to just print out the name of the object's class. If we want to print out something else (like the numerator followed by a slash followed by the denominator, we have to provide our own ToString method). Add this to the Fraction class:

public String ToString() { return numerator.ToString() + "/" + denominator.ToString(); }

Modify your main program to look like this: Fraction f1 = new Fraction(); Console.WriteLine("f1: " + f1.ToString());

Console.WriteLine("f1: " + f1);

5/6/2023 document.docx Page 2 of 23

This works, but notice that when we explicitly call ToString (in the first WriteLine) that it prints what we want. But when we don't explicitly call ToString (in the second WriteLine) that it does not print what we want! We get this:

f1: 0/0 f1: FractionTest.Fraction

Notice that we also get a warning message:'FractionTest.Fraction.ToString()' hides inherited member 'object.ToString()'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword.

C# allows programmers to write their own ToString method and have it override the existing version of ToString. Since there is already a ToString method, the one we write will need to override the existing ToString method. Note the word override below. The method is exactly the same; the only change is that we have added the override keyword.

public override String ToString()

{ return numerator.ToString() + "/" + denominator.ToString(); }

Now run your program. Your main program should still look like this: Fraction f1 = new Fraction(); Console.WriteLine("f1: " + f1.ToString()); Console.WriteLine("f1: " + f1);

Note that both WriteLine statements produce what we expected (although a fraction with a denominator of 0 is a bit of a problem; we'll look at that later).

f1: 0/0 f1: 0/0

Constructors and Overloading

A constructor is a special method that gets executed when an object is created. Even if we do not specify a constructor, C# still creates a default constructor for us. The default constructor will set numeric variables to 0 and string variables to the empty string and boolean variables to false. This is a problem for us because we don't ever want the denominator of a fraction to be 0! Fortunately, C# allows us to write our own constructors.One common use of a constructor is to create multiple ways of creating a new object -- overloading the constructor.Overloading: creating more than one procedure or function with the same name (but different argument lists).It seems like there should be 3 ways to initialize a fraction:

(1) Default: initialize numerator to 0 and denominator to 1.(2) A constructor to initialize both the numerator and denominator.(3) A constructor to set the fraction to a whole number. Set the denominator to 1.

5/6/2023 document.docx Page 3 of 23

Such constructors would be used like this:Fraction f2 = new Fraction(); // creates 0/1Fraction f3 = new Fraction(1, 2); // creates 1/2Fraction f4 = new Fraction(5); // creates 5/1

C# allows us to overload any sub or function. Note that whenever you call an overloaded built-in method in C#, you get a yellow box that pops up with the note "1 of n". The "n" is the number of different versions of this method that exist. How does C# know which of these many methods you intend to call? It looks at the parameter list. The parameter list for each overloaded method must be unique. This is called the signature of the method. So if your method call lists 3 strings as parameters, there can only be one method with that name that has exactly 3 strings as parameters, and that is the one that will be called. If you try to create a signature for a method that is identical to an existing signature, you will get a syntax error. A constructor looks just like any other method, except that it does not have a return type, and its name is always the same as the name of the class. So all of our constructors will be named Fraction. Add the following code to your Fraction class. By convention, the constructors are listed before any other methods (but after any variable declarations).

//Default constructor public Fraction() { numerator = 0; denominator = 1; }

// Constructor that accepts two ints public Fraction(int top, int bottom) { numerator = top; denominator = bottom; }

// Constructor that accepts a single int public Fraction(int top) { numerator = top; denominator = 1; }

The second constructor can cause an illegal value for the fraction if the denominator is 0. This should not be allowed. But we will consider this later. Test your constructors with the following code:

Fraction f2 = new Fraction(); // creates 0/1 Fraction f3 = new Fraction(1, 2); // creates 1/2 Fraction f4 = new Fraction(5); // creates 5/1 Fraction f5 = new Fraction(1, 0) // creates 1/0!

5/6/2023 document.docx Page 4 of 23

Console.WriteLine(f2); Console.WriteLine(f3); Console.WriteLine(f4); Console.WriteLine(f5);

Your output should be:0/11/25/11/0

Methods: ToDouble()

In addition to printing the fraction as a fraction, we may also want to see its decimal value. We can write a ToDouble method:

public double ToDouble() { return ((double) numerator / (double) denominator); }

Then call it from your main program. Add the following at the bottom of your program: Console.WriteLine("f2: " + f2.ToDouble()); Console.WriteLine("f3: " + f3.ToDouble()); Console.WriteLine("f4: " + f4.ToDouble()); Console.WriteLine("f5: " + f5.ToDouble());

The output is: F2: 0.0 F3: 0.5 F4: 5.0 F5: Nan

NaN stands for "not a number" and we are getting this because when you divide by 0, the result is undefined (not a number).We can fix this (for now) by making sure that the denominator is not 0. Add the following to your 2-parameter constructor:

public Fraction(int top, int bottom) { numerator = top;

if (bottom != 0) denominator = bottom; else bottom = 1;

}

Run your program again.

5/6/2023 document.docx Page 5 of 23

Arithmetic: Times(f).

We want to be able to write statements like this: f1 = f2.Times(f3);

In the above statement, we will call f2 the receiving object and f3 the argument object. Both f2 and f3 will have a Times method, but the above statement is calling f2's Times method. So f2 is the receiving object and f3 is the argument object. If the instruction were written like this:

f1 = f3.Times(f2);

then f3 would be the receiving object and f2 would be the argument object. So let's add some code to our Fraction class. If the Times method is invoked with the following statement:

f1 = f2.Times(f3);

f2 (the receiver) will be referred to as this in the code below, and f3 will be referred to as f. Our method needs to do the following: (1) accept an argument, (2) create a new Fraction object to hold the result, (3) multiply the numerators of this and f together to get the numerator of the result, (4) multiply the denominators of this and f together to get the denominator of the result, and finally, (5) return the result. Like this:

public Fraction Times(Fraction f) { Fraction result = new Fraction(); result.numerator = this.numerator * f.numerator; result.denominator = this.denominator * f.denominator; return result; }

Note that we can use the private variables here because we are writing code inside of the Fraction class file.Test the method by adding code to your main program:

Fraction f1 = new Fraction(); f1 = f3.Times(f4); Console.WriteLine("f1: " + f1.ToString());

5/6/2023 document.docx Page 6 of 23

More MethodsReducing Fractions

When we do arithmetic on fractions, we can get some funny answers. Try the following example:

f1 = new Fraction(1, 4); f2 = new Fraction(3, 8); f3 = f1.Plus(f2); Console.WriteLine("f3: " + f3);

1/4 + 3/8 = 20/32! We are not reducing any of our answers. We might want to reduce our fractions. Let's add two methods:

Reduce()

This is a void method (or a procedure method) – reduces the fraction to lowest terms. It CHANGES the numerator and denominator.We need a GCD function. Search the Internet for the algorithm. From Wikipedia (this is Euclid’s algorithm):

function gcd(a, b) while b ≠ 0 t := b b := a mod b a := t return a

Convert to C#: private int gcd(int a, int b) { int t; while (b != 0) { t = b; b = a % b; a = t; } return a; }

public void Reduce() { int divisor = gcd(numerator, denominator); numerator = numerator / divisor; denominator = denominator / divisor;

5/6/2023 document.docx Page 7 of 23

}

Arithmetic: DividedBy(f). A Method that can cause an error

We also want to be able to write statements like this: f1 = f2.DividedBy(f3);

Again, in this example, f2 is the receiver object (referred to as this in the method), and f3 is the argument object. The DividedBy method needs to do the following: (1) accept an argument, (2) create a new Fraction object to hold the result, (3) set the result's numerator to the product of this's numerator and the argument's denominator, (4) set the result's denominator to the product of this's denominator and the argument's numerator, (5) return the result. We should NOT allow the user to divide by 0! But we will worry about this later. Add the following to your Fraction class:

public Fraction DividedBy(Fraction f) { Fraction result = new Fraction(); result.numerator = this.numerator * f.denominator; result.denominator = this.denominator * f.numerator; return result; }

Note that when doing the arithmetic methods that neither the receiving fraction object nor the parameter is changed. Instead a new fraction object is created and returned.Test your DividedBy method by adding the following to your main method:

f1 = new Fraction(1,2); f2 = new Fraction(1,3); f1 = f2.DividedBy(f3); // Divide 1/2 by 1/3. Result should be 3/2. Console.WriteLine("f1: " + f1.ToString());

5/6/2023 document.docx Page 8 of 23

Reduced()

This is a function that returns the reduced form of the fraction without changing the receiver. Note that this function returns a fraction, so we have to create our own "result" fraction to hold the result to be returned.

public Fraction Reduced() { int divisor = gcd(numerator, denominator); Fraction result = new Fraction(); result.numerator = this.numerator / divisor; result.denominator = this.denominator / divisor; return result; }

Arithmetic: Plus(f)

Now it should be easy for us to write the Plus and Minus methods. The Plus method will return a fraction that is the sum of the local fraction object and the fraction f.

public Fraction Plus (Fraction f) { Fraction result = new Fraction(); result.numerator = this.numerator * f.denominator + f.numerator * this.denominator; result.denominator = this.denominator * f.denominator; return result; }

Test your code: f1 = f2.Plus(f3); // 1/2 + 1/3 = 5/6 Console.WriteLine("f1: " + f1.ToString());

Arithmetic: Minus(f).

Returns a fraction that is the local fraction object minus the parameter fraction f. Add the following to your Fraction class:

public Fraction Minus (Fraction f) { Fraction result = new Fraction(); result.numerator = this.numerator * f.denominator – f.numerator * this.denominator; result.denominator = this.denominator * f.denominator; return result ; }

NOTE: We also could have written the body as one statement like this:Return new Fraction(This.Numerator * f.Denominator - f.Numerator * This.Denominator, bottom = This.Denominator * f.Denominator)

Test your code in main:

5/6/2023 document.docx Page 9 of 23

f1 = f2.Minus(f3); // 1/2 - 1/3 = 1/6 Console.WriteLine("f1: " + f1.ToString());

Equals

Every class that you create comes with two built-in methods: (1) ToString and (2) Equals. However, the Equals method probably does not do what you want it to do. Try it:

f1 = new Fraction(1,2);f2 = new Fraction(1,2);Console.WriteLine(f1.Equals(f2));

The problem with the built-in Equals method has to do with the way that objects are implemented in C#. Remember that when you declare an object (like f1 or f2), that you are only allocating enough memory for an address. Later when the memory for the objects is actually allocated (with the new keyword), the address of the newly-allocated memory is what goes in the memory space set aside for the variable. Like this:Declaring f1 and f2:

Fraction f1, f2;

This gives us this in memory:

Neither variable has been assigned a value, and both are said to have a "null" value. However, when we allocate memory for f1 and f2, the address of the allocated memory cells is put if f1 and f2. Like this:

5/6/2023 document.docx Page 10 of 23

The arrows represent memory addresses. Since f1 is located at one location in memory and f2 is located at another location in memory, the values of f1 and f2 (addresses) are not the same. It doesn't matter what the values of the actual objects are (the circles); C# only looks at what's in f1 and f2, and they are clearly different values. So two objects are almost never considered "equal" by C#, even if the objects hold the same values. C# is just comparing the memory addresses. This is probably not what we want. If we are comparing two fractions, we want to know if the numerators are equal and if the denominators are equal. So we will have to write our own Equals method.

Overriding and overloading existing methods

Equals(f). Returns true if the fraction f is equal to the local fraction object, false otherwise. Note that this function must also override an inherited method. However, this time, we must put the word overloads after the word "public". This is because the built-in equals method expects an object to be passed in—not a fraction. Overloading a method means that we are using the same name as the existing method, but we are using a different signature. The "signature" of a method is the combination of its name and its parameter list. You can use the same method name more than once if you have a different parameter list each time.NOTE: Only do the "StrictEquality=True" part at first.

public bool Equals(Fraction f) { if (f.numerator == this.numerator && f.denominator == this.denominator) return true; else return false; }

5/6/2023 document.docx Page 11 of 23

Shared Variables: Problems with the Equals method

How do we decide whether two fractions are equal? Are the fractions 2/3 and 4/6 equal? Numerically, they have the same value, so the answer would be "yes". But if we define equal as having the same numerator and the same denominator, then the answer is "no". We can define this by creating a property called StrictEquality.StrictEquality. A Boolean property. Default value is false. If set to true, the Equals method returns true only if both numerators are equal and both denominators are equal. If set to false, the Equals method returns true if the reduced versions of each fraction are equal. For example, if StrictEquality is true, and we compare the fractions 1/2 and 2/4, we will get false. If StrictEquality is false and we compare 1/2 and 2/4, we will get true.If we have a StrictEquality property for each fraction, then we have to set its value for every fraction. And what if we have this:

f1.StrictEquality = true;f2.StrictEquality = false;

When we compare f1 and f2, do we use strict equality or not? A better way would be if we have just a single variable for the entire class that can be set once. Such a variable is called a shared variable. Shared variable: A variable that is shared by all members of a class. A single copy of the variable exists, whereas a non-shared variable has one copy for each object that is created. Shared variables are accessed by using their class name – not their object name, like this:

Fraction.StrictEquality = true;

How do we decide whether to reduce our answers or not? One simple way is to have a variable called AlwaysReduce that is either true or false. However, if we make this an ordinary property variable, then we have to set it to true for every single fraction that we create:

f1.AlwaysReduce = true;f2.AlwaysReduce = true; // etc.

So AlwaysReduce should be a shared property, too! Default value is false. If set to true, the four arithmetic methods will return a reduced answer. If set to false, they will not reduce.

Declaring Shared Variables

Declare a shared variable like this:private static bool strictEquality = false;private static bool alwaysReduce = false;

C# does not use the word "shared" to indicate a shared variable. Instead, it uses the word "static". There are two types of variables: static variables and "instance" variables. If a variable is static, there is one copy of the variable for the entire class. If a variable is an instance variable, then there is one copy of the variable for each object that is created.Implement its property methods in the usual way:

public static bool StrictEquality

5/6/2023 document.docx Page 12 of 23

{ get { return strictEquality; } set { strictEquality = value; }}

public static bool AlwaysReduce{ get { return alwaysReduce; } set { alwaysReduce = value; }}

Fixing our Equals method

We also need to rewrite our Equals method to account for Strict Equality.Add some code to the end of the Equals method:

public bool Equals(Fraction f){ if (strictEquality) if (f.numerator == this.numerator && f.denominator == this.denominator) return true; else return false; else //StrictEquality = false if (f.ToDouble()== this.ToDouble()) return true; else return false;}

Fixing our arithmetic method(s)

We need to fix our arithmetic methods now to see if they have to always reduce their answers or not (we have only written one arithmetic method: times. But if we had written the other three, we would have to add this code to the end of each of those methods as well. At the end of each arithmetic method, add this:

if (AlwaysReduce){ result.Reduce(); return result;}

5/6/2023 document.docx Page 13 of 23

Comparing Fractions

Relational Operators

In addition to the Equals method, we may also want to do other comparisons of fractions. 1. Not equals2. Greater than3. Greater than or equal to4. Less than5. Less than or equal to

There is no need to write a NotEquals method. We can just use the equals method with a "not" operator in front of it:

if ( ! f1.Equals(f2)) // Do something

GreaterThan method

Sometimes GT is used for greater than.public bool GT(Fraction other){ if (this.ToDouble() > other.ToDouble()) return true; else return false;}

GreaterThanOrEqualTo method

Sometimes GE is used for greater than or equal to.public bool GE(Fraction other){ if (this.ToDouble() >= other.ToDouble()) return true; else return false;}

LessThan method

Sometimes LT is used for less than.public bool LT(Fraction other){ if (this.ToDouble() < other.ToDouble()) return true; else return false;

5/6/2023 document.docx Page 14 of 23

}

LessThanOrEqualTo method

Sometimes LE is used for greater than or equal to.public bool LE(Fraction other){ if (this.ToDouble() <= other.ToDouble()) return true; else return false;}

All of the above if statements could be replaced by a single return statement.We also could have written the last three methods like this:

GreaterThanOrEqualTo method

public bool GE(Fraction other) { return this.GT(other) || this.Equals(other); }

LessThan method

public bool LT(Fraction other) { return ! (this.GE(other)); }

LessThanOrEqualTo method

public bool LE(Fraction other) { return ! this.GT(other); }

5/6/2023 document.docx Page 15 of 23

Zero denominators

We could just set the denominator to some default value (e.g. 1) if a 0 is passed in, but that's not the best way to handle it. The best way is to "throw" an exception. An exception is a serious error that causes your program to terminate with an error message. Make the following modification to the constructor that accepts the numerator and denominator:

public Fraction(int top, int bottom){ numerator = top; if (bottom == 0) throw new Exception("Denominator cannot be 0!");

denominator = bottom;}

It is a bad thing to have your program blow up!To keep your program from blowing up, add a Try-Catch block (see below) around any code that might possibly throw an exception. This solves the problem of creating an illegal fraction, but what about this:

Fraction f1 = new Fraction (1,2);f1.Denominator = 0;

This is clearly an error, but it is legal! And this brings us to one of the advantages of objects: the property methods of objects allow us to check any value that is assigned to a private variable before it gets assigned, and to disallow some values if they are inappropriate. We know that we never want a denominator to be 0. So we can modify the Denominator's set method to disallow a value of 0. Make the following change to the Denominator set property procedure.

public int Denominator{ get { return denominator; } set { if (value == 0) throw new Exception ("Denominator cannot be 0!"); else denominator = value; }

}

5/6/2023 document.docx Page 16 of 23

Try-Catch blocks

A Try-Catch block is used whenever you have a statement that might cause a run-time error. The syntax is:

try{

<statements that might cause an error>}catch [(<Exception type> <var>)]{

<statements to execute when an error occurs>}[finally{

<statement that are ALWAYS to be executed, whether there was an error or not>

}]Example

try{Fraction f = new Fraction(1, 0);}catch (Exception e){Console.WriteLine(e.Message);}

NOTE: Try/Catch is used to make a program terminate gracefully without doing something illegal. This is not normally something that games are concerned about. The user doesn't want to stop in the middle of a game no matter what!

5/6/2023 document.docx Page 17 of 23

Read-only properties and negative numbers

Another problem we might have is handling negative fractions. How do we store the value -1/2? Is the numerator -1? Or is the denominator -2? And does it even matter?Are these two fractions the same? How should we represent negative numbers?

Fraction f1 = new Fraction(-1, 2); Fraction f2 = new Fraction(1, -2);

Also, consider this. Are these two fractions the same? Fraction f3 = new Fraction(1,2); Fraction f4 = new Fraction(-1, -2);

It might be useful to define a "normal" form for fractions: all negative fractions will be represented with a negative value for the numerator and a positive value for the denominator. One way to enforce this is to prevent the user from changing the numerator or denominator after the fraction has been created. (This is not the only solution.)Then, whenever we create a new fraction or modify an existing one, we just need to make sure that we keep it in this standard form.One advantage of objects is the possibility of having read-only properties. A read-only property is a value that can be read, but can never be written to (assigned a value). Of course, it makes no sense to have a property that is 100% read-only. If that were the case, we would never be able put a value in the variable! So a read-only property is a property whose value can be set once (in the constructor), and can never be changed (by the user) after that (however, it may be changed within the class itself). In our case, we will allow the denominator's value to be set only in the constructor (and, of course, in arithmetic assignment statements). If we wanted to make our denominators read-only, we would simply remove the set method:

public int Denominator{ get { return denominator; }}

And we could do the same for the numerator:public int Numerator{ get { return numerator; }}

Note that we must also modify our constructors so that we can pass negative numbers in and still keep the numbers in the proper format (only numerator can be negative).NOTE: There are 4 cases:

both n and d are positive (OK) both n and d are negative (change signs of BOTH) n is positive and d is negative (change signs of BOTH) n is negative and d is positive (OK)

5/6/2023 document.docx Page 18 of 23

NOTE: In the following code, it may be a good idea to use parentheses, even though they are not required.

private void Normalize(Fraction f){ if (f.numerator<0 && f.denominator<0 || f.numerator>0 && f.denominator<0) { f.numerator = -f.numerator; f.denominator = -f.denominator; }}

The Normalize method will take any fraction and normalize it. Now we just need to make sure that every time we create a fraction (constructors) or every time we do arithmetic on a fraction we call the Normalize method.Add calls to Normalize at the end of the following methods:

1. The constructor that accepts two integer arguments.2. All four arithmetic methods.

We could do the same thing for the numerator if we wanted to make it difficult for somebody to change the numerator of a fraction. See the next section.

5/6/2023 document.docx Page 19 of 23

Operator Overloading!

// ************************************************************ // Examples of operator overloading! // Kind of cool, but of questionable value. // ************************************************************ // Arithmetic operator overloading

public static Fraction operator *(Fraction f1, Fraction f2) { Fraction f3 = new Fraction(); f3.numerator = f1.numerator * f2.numerator; f3.denominator = f1.denominator * f2.denominator; if (alwaysReduce) f3.Reduce(); return f3; }

public static Fraction operator /(Fraction f1, Fraction f2) { Fraction f3 = new Fraction(); if (f2.numerator == 0) throw new Exception("Division by 0!"); f3.numerator = f1.numerator * f2.denominator; f3.denominator = f1.denominator * f2.numerator; if (alwaysReduce) f3.Reduce(); return f3; }

public static Fraction operator +(Fraction f1, Fraction f2) { Fraction f3 = new Fraction(); f3.numerator = f1.numerator * f2.denominator + f2.numerator * f1.denominator; f3.denominator = f1.denominator * f2.denominator; if (alwaysReduce) f3.Reduce(); return f3; }

public static Fraction operator -(Fraction f1, Fraction f2) { Fraction f3 = new Fraction(); f3.numerator = f1.numerator * f2.denominator - f2.numerator * f1.denominator; f3.denominator = f1.denominator * f2.denominator; if (alwaysReduce) f3.Reduce();

5/6/2023 document.docx Page 20 of 23

return f3; } // Relational operator overloading.

public static bool operator ==(Fraction f1, Fraction f2) { if (f1.numerator * f2.denominator == f1.denominator * f2.numerator) return true; else return false; }

public static bool operator !=(Fraction f1, Fraction f2) { return !(f1 == f2); }

public static bool operator >(Fraction f1, Fraction f2) { return f1.ToDouble() > f2.ToDouble(); }

public static bool operator <(Fraction f1, Fraction f2) { return f1.ToDouble() < f2.ToDouble(); }

public static bool operator >=(Fraction f1, Fraction f2) { return f1.ToDouble() >= f2.ToDouble(); }

public static bool operator <=(Fraction f1, Fraction f2) { return f1.ToDouble() <= f2.ToDouble(); } }

Static properties are NOT needed in our implement

5/6/2023 document.docx Page 21 of 23

The Fraction class, continued

Code to test the fraction class:static void Main(string[] args){ Fraction f1 = new Fraction(); // f1 is 0/1 Fraction f2 = new Fraction(2); // f2 is 2/1 Fraction f3 = new Fraction(3, 4); // f3 is 3/4 Fraction f4 = new Fraction(1, 2); // f4 is 1/2

Console.WriteLine("0: " + f1.ToDouble()); Console.WriteLine("2: " + f2.ToDouble()); Console.WriteLine(".75: " + f3.ToDouble()); Console.WriteLine(".5: " + f4.ToDouble());

Console.WriteLine("0/1: " + f1.ToString()); Console.WriteLine("2/1: " + f2.ToString()); Console.WriteLine("3/4: " + f3.ToString()); Console.WriteLine("1/2: " + f4.ToString()); Console.WriteLine("11/4: " + f2.Plus(f3).ToString()); Fraction f5 = (f2 * f3); Console.WriteLine("3/8:* " + (f3 * f4).ToString()); Console.WriteLine("false: " + f2.Equals(f3).ToString()); Console.WriteLine("3/8: " + f3.DividedBy(f2).ToString()); Console.WriteLine("3/4: " + f3.ToString()); Console.WriteLine("3/8: " + f3.Times(f4).ToString()); Console.WriteLine("6/4: " + f3.DividedBy(f4).ToString()); Console.WriteLine("3/2: " + f3.DividedBy(f4).Reduced().ToString()); Console.WriteLine("10/8: " + f3.Plus(f4).ToString()); Console.WriteLine("2/8: " + f3.Minus(f4).ToString()); Console.WriteLine("1/4: " + f3.Minus(f4).Reduced().ToString()); Fraction.AlwaysReduce = true; Console.WriteLine("1/4: " + f3.Minus(f4).ToString()); Console.WriteLine("False: " + f4.Equals(f3).ToString()); Fraction.StrictEquality = true; f1 = new Fraction(1, 2); f2 = new Fraction(2, 4); Console.WriteLine("False: " + f1.Equals(f2).ToString()); Fraction.StrictEquality = false; f1 = new Fraction(1, 2); f2 = new Fraction(2, 4); Console.WriteLine("True: " + f1.Equals(f2).ToString());

5/6/2023 document.docx Page 22 of 23

f1 = new Fraction(1, 2); f2 = new Fraction(1, 2); if (f1.Equals(f2)) Console.WriteLine("Equal"); else Console.WriteLine("Not Equal");

f1 = f2; if (f1.Equals(f2)) Console.WriteLine("Equal"); else Console.WriteLine("Not Equal");

f1.Denominator = 3;

Console.WriteLine("f1: " + f1.ToString()); Console.WriteLine("f2: " + f2.ToString());

Console.ReadLine();

}

5/6/2023 document.docx Page 23 of 23