working code :D

/*
Name: Samar Qureshi
Date: 12/11/21
Description: The Fractions class part of the Numbers project is able to work with and manipulate a Fraction object, which is retrieved from the
client code, FractionMain. Capabilities include, but are not limited to: basic math operations (addition, subtraction, multiplication,
division, reciprocating, simplifying), as well as other advanced functions such as elegant format (done by Jennifer), converting from a 
mixed number to an improper fraction and vice versa, evaluating exponents of a fraction, converting from a decimal to fraction and vice 
versa, and sorting a group of fractions from greatest to least (found in the client code).
*/

public class Fraction {
    //instance variables
    private long denominator, numerator, wholeNumber, mixedNumerator;
    private int numeratorExponent, denominatorExponent;
    private boolean mixedNeg, wholeNeg, denomNeg;

    public Fraction(){ //constructor
        numerator = 0;
        denominator = 1;
        wholeNumber = 0;
        mixedNumerator = 0;
        numeratorExponent = 1;
        denominatorExponent = 1;
        mixedNeg = false;
        wholeNeg = false;
    }

    public Fraction(long newNumerator, long newDenominator){ //overloaded constructor for regular fractions
        numerator = newNumerator;
        denominator = newDenominator;
    }

    public Fraction(long newWholeNumber, long newMixedNumerator, long newDenominator){ //overloaded constructor for mixed numbers
        mixedNumerator = newMixedNumerator;
        denominator = newDenominator;
        wholeNumber = newWholeNumber;
        
    }

    // Takes in a long value, then sets it as the numerator
    public void setNumerator(long newNumerator){
        numerator = newNumerator;
    }

    //Returns the numerator as a long value
    public long getNumerator(){ 
        return numerator;
    }

    //Takes in a long value, then sets it as the denominator
    public void setDenominator(long newDenominator){ 
        denominator = newDenominator;
    }

    //Returns the denominator as a long value
    public long getDenominator(){ 
        return denominator;
    }

    //Takes in a long value, then sets it as the whole number in a mixed fraction
    public void setWholeNumber(long newWholeNumber){
        wholeNumber = newWholeNumber;
    }
    //Returns the whole number of a mixed fraction as a long value
    public long getWholeNumber(){
        return wholeNumber;
    }

    //Takes in a long value, then sets it as the numerator in a mixed fraction
    public void setMixedNumerator(int newMixedNumerator){
        mixedNumerator = newMixedNumerator;
    }

    //Returns the numerator of a mixed fraction as a long value
    public long getMixedNumerator(){
        return mixedNumerator;
    }

    public void setNumeartorExponent(int newNumeratorExponent){
        numeratorExponent = newNumeratorExponent;
    } 

    ////Returns the exponent of the numerator as an integer
    public int getNumeratorExponent(){
        return numeratorExponent;
    }

    //Takes in a an integer value, then sets it as the exponent of the denominator 
    public void setDenominatorExponent(int newDenominatorExponent){
        denominatorExponent = newDenominatorExponent;
    }

    //Returns the exponent of the denominator as an integer 
    public int getDenominatorExponent(){
        return denominatorExponent;
    }

    /*
    Adds two fraction objects together, creates a common denominator by "cross multiplying", and then calls on the
    simplify() method to reduce the fraction to its lowest terms 
    Pre: two fraction objects that are inputted by the user
    Post: resulting fraction object is the sum of the two user inputted fraction objects, does not return a fraction,
    rather, changes the value of the numerator and denominator of the original fraction object so that those instance 
    variables can now be passed to formatFraction()
    */
    public void add(Fraction frac2){
        numerator = (frac2.getDenominator()*numerator) + (denominator*frac2.getNumerator());
        denominator *=frac2.getDenominator();
        simplify();
        //frac1 becomes the sum
    }

    /*
    Subtracts two fraction objects, creates a common denominator by "cross multiplying", and then calls on the
    simplify() method to reduce the fraction to its lowest terms 
    Pre: two fraction objects that are inputted by the user
    Post: resulting fraction object is the difference of the two user inputted fraction objects, does not return a fraction,
    rather, changes the value of the numerator and denominator of the original fraction object so that those instance 
    variables can now be passed to formatFraction()
    */
    public void subtract(Fraction frac2){
        numerator = (frac2.getDenominator()*numerator) - (denominator*frac2.getNumerator());
        denominator *=frac2.getDenominator();
        simplify();
        //frac1 becomes the difference
    }

    /*
    Multiplies two fractions together by finding the product of the two numerators, and the product of the two denominators,
    then calls on the simplify() method that reduces the fraction object to its simpliest form
    Pre: two fraction objects that are inputted by the user
    Post: resulting fraction object is the product of the two user inputted fraction objects, does not return a fraction,
    rather, changes the value of the numerator and denominator of the original fraction object so that those instance 
    variables can now be passed to formatFraction()
    */
    public void multiply(Fraction frac2){
        numerator *= frac2.getNumerator();
        denominator *= frac2.getDenominator();
        simplify();
    }
    
    /*
    Divides two user inputted Fraction objects by eachother the same way as the multiply() method, except the first fraction
    object is now being multiplied by the reciprocal of the second fraction object
    Pre: two fraction objects that are inputted by the user
    Post: resulting fraction object is the quotient of the two user inputted fraction objects, does not return a fraction,
    rather, changes the value of the numerator and denominator of the original fraction object so that those instance 
    variables can now be passed to formatFraction()
    */
    public void divide(Fraction frac2){
        numerator *= frac2.getDenominator();
        denominator*=frac2.getNumerator();
        simplify();
    }

    /*
    Simplifies a fraction by determining the greatest common factor between in the numerator and denominator, then divides
    the the numerator and denominator by the the GCF, which results in the reduced form of the fraction object
    Pre: fraction object 
    Post: the reduced or simplified form of the same Fraction object
    */
    public void simplify(){ 
        //evaluateExponents();

        long gcf = gcf(denominator, numerator); //initally reducing the Fraction
        numerator /= gcf;
        denominator /= gcf;
        mixedNumerator/=gcf;

        if(numerator < 0  && denominator < 0){  
        // if the numerator and denominator are negative, it reduces to a fully postive Fraction
            numerator = Math.abs(numerator);
            // mixedNumerator = Math.abs(mixedNumerator);
            denominator = Math.abs(denominator);
        }

        if(mixedNumerator < 0  && denominator < 0){ 
            // if the numerator in a mixed fraction and denominator are negative, it reduces to a fully postive Fraction
            mixedNumerator = Math.abs(mixedNumerator);
            denominator = Math.abs(denominator);
        }

        if(mixedNumerator < 0 && wholeNumber < 0){
            // if the wholeNumber of a mixed fraction and denominator are negative, it reduces to a fully postive Fraction
            mixedNumerator = Math.abs(mixedNumerator);
        }
        
        if(numerator%denominator==0){ 
            //if the fraction can be reduced to a whole number
            wholeNumber = numerator/denominator;
            // numerator = 0;
            // denominator = 1;
        } 

    }

    /*
    Determines the greatest common factor of two numbers (usually the numerator and the denominator), which is then utilized in the
    simplify() method to reduce the Fraction object
    Pre: takes in two numbers as long data types
    Post: returns a single long, which is the greatest common factor or divisor between the two numbers
    */
    public long gcf(long num1, long num2){
        while(Math.abs(num2)>0){  
            //keeps traversing through until it has found the greatest number that is able to divide evenly into num1 and num2
            long temp = num2;
            num2 = num1%num2;
            num1 = temp;
        }
        return num1;
    }

    /*
    Overrides the .equals() method for Objects, is rewritten in such a way that it can be utilized to determine if two fraction objects are 
    equal to eachother, regardless if they have the exact same initial numerator and denominator
    Pre: Takes in the current Fraction object, along with a second Fraction object as a parameter
    Post: returns a boolean value based off of the comparison between the two Fraction objects
    */
    public boolean equals(Fraction frac){
        //checks to see if two fractions are equal to eachother
        simplify(); //simplifies the original fraaction object
        frac.simplify(); //simplifies the parameter fraction object

        if(numerator==frac.getNumerator() && denominator==frac.getDenominator()){
            return true;
        }
        return false;
    }

    /*
    Converts a mixed number into an improper fraction
    Pre: takes in the whole number, the numerator (mixedNumerator), and the denominator, all inputted by the user in FractionMain
    Post: modifies the numerator and denominator of the Fraction in which it is the correct improper fraction; then is simplified
    before being passed to formatImproper
    */
    public void mixedToImproper(){
        if(numerator < 0 || mixedNumerator < 0 || wholeNumber < 0){ //checks if the numerator is negative
            mixedNeg = true;
        }

        // if(wholeNumber < 0){
        //     wholeNeg = true;
        // }

        if(denominator < 0){
            denomNeg = true;
        }

        numerator = Math.abs(mixedNumerator) + Math.abs(wholeNumber*denominator);
        wholeNumber = 0;
        
        simplify();
    }

    /*
    Converts an improper fraction into a mixed number
    Pre: takes in the numerator and denominator using accessor methods
    Post: simpifies the improper fraction, then modifies the mixedNumerator, the wholeNumber and the denominator to the equivalent
    mixed number fraction before being passed to formatMixed
    */
    public void improperToMixed(){
        simplify(); // cancel out negatives if needed

        wholeNumber = numerator/denominator; // long (data type) division truncates
        
        // the given fraction was negative but since the whole number is 0, we must make sure a negative will be displayed.
        if (numerator < 0 || denominator < 0) {
        	wholeNeg = true;
        }

        // the numerator & denominator should be positive since the whole number will be negative if necessary
        mixedNumerator = (long) Math.abs(numerator % denominator);
        denominator = (long) Math.abs(denominator);
        
    }

    /*
    Evalutates exponents of the numerator and denominator
    Pre: utilizes accessor methods to get the numerator and denomaintor, as well as their respective exponents
    Post: the resulting numerator and denominator instance variables after evalutaing the their exponents
    */
    public void evaluateExponents(){
        if(numeratorExponent > denominatorExponent && numerator == denominator){ //if the bases are the same
            numeratorExponent -= denominatorExponent;
        }

        else if(denominatorExponent > numeratorExponent && numerator == denominator ){
            denominatorExponent -= numeratorExponent;
        }

        //manually evaluating the exponents, regardless of the bases in relation to their respective exponent
        numerator = (long)Math.pow(numerator, numeratorExponent);
        denominator = (long)Math.pow(denominator, denominatorExponent);
    }

    /*
    Finds the reciprocal of a Fraction object
    Pre: utilizes accessor methods to get the numerator and the denominator
    Post: modifies the numerator and denominator so that their values match the expected reciprocal 
    */
    public void reciprocate(){
        if(wholeNumber > 0){
            mixedToImproper();
            numerator = denominator;
            denominator = mixedNumerator;
        }

        else{
            numerator = denominator;
            denominator = numerator;
        }
    }

    /*
    One line simple output for any Fraction object, as opposed to formatFraction
    Pre: takes the Fraction object
    Post: returns the Fraction object in its respective String format
    */
    public String toString(){ 
        //slow build the fraction
        if(wholeNumber!=0){
            return wholeNumber + " " + (mixedNumerator + "/" + denominator);
        }
        return(numerator + "/" + denominator);

    }

    /*
    JENNIFER'S METHOD: Outputs the numerator and denominator in an elegant three line manner
    Pre: numerator and denominator instance variables as longs
    Post: a 2D String array that graphically depicts the expected output 
    */
    public String[] formatFraction(long numerator, long denominator) {
		// declarations & initializations
		String[] returnArr = new String[3];
		int lengthN = (numerator + "").length();
		int lengthD = (denominator + "").length();
		int length;
		
		// find length of fraction bar
		if (lengthN > lengthD) {
			length = lengthN + 2;
		} else {
			length = lengthD + 2;
		}
		
        returnArr = setEmptyStrings(returnArr);

		// find out which value has more digits (numerator vs. denominator) & format the numerator & denominators accordingly
		if (lengthN > lengthD && (lengthN - lengthD) % 2 == 1) {
			returnArr[0] = getSpaces((length - lengthN) / 2) + numerator + getSpaces((length - lengthN) / 2);
			returnArr[2] = getSpaces((length - lengthD) / 2) + denominator + getSpaces((length - lengthD) / 2 + 1); // +1 space right of the denominator to ensure that everything after it will continue to be centered
		} else if ((lengthD - lengthN) % 2 == 1) {
			returnArr[0] = getSpaces((length - lengthN) / 2) + numerator + getSpaces((length - lengthN) / 2 + 1); 
			returnArr[2] = getSpaces((length - lengthD) / 2) + denominator + getSpaces((length - lengthD) / 2);
		} else {
			returnArr[0] = getSpaces((length - lengthN) / 2) + numerator + getSpaces((length - lengthN) / 2);
			returnArr[2] = getSpaces((length - lengthD) / 2) + denominator + getSpaces((length - lengthD) / 2);
		} 
		
		// format fraction bar based on length (add hyphens)
		for (int i = 0; i < length; i++) {
			returnArr[1] += "-";
		}
		
		return returnArr;
	}

    /*
    JENNIFER'S METHOD: returns String containing the specified number of spaces
    Pre: integer numSpaces; specifies number of spaces
    Post: returns empty string 
    */
    public String getSpaces(int numSpaces) {
		// declare & initialize an empty string to be returned at the end
		String returnStr = "";
		
		// add spaces to the return string
		for (int i = 0; i < numSpaces; i++) {
			returnStr += " ";
		}
		
		return returnStr;
	}

    /*
    JENNIFER'S METHOD: fills up String array with empty spaces
    Pre: null String array
    Post: String array with "" in every index
    */
    public static String[] setEmptyStrings(String[] arr) {
		// traverse through every element in the String array, and sets each index to an empty String
		for (int i = 0; i < arr.length; i++) {
			arr[i] = "";
		}
		
		return arr;
	}

    /*
    Formats a mixed fraction by calling on formatFraction()
    Pre: takes in the instance variables of mixedNumerator, denominator and the whole number 
    Post: graphically formats them in a 2D String array from formatFraction()
    */
    public String[] formatMixed(){
    	// NO NEED SINCE THE WHOLE NUMBER WAS ALREADY CALCULATED; IF IT'S NEGATIVE, THEN IT'LL ALEADY BE NEGATIVE
        //formats correctly if there is a negative needed that was found in improperToMixed()
        if(wholeNeg && wholeNumber == 0){
            return formatFraction(-mixedNumerator, denominator); // numerator will be negative
        }
        return formatFraction(mixedNumerator, denominator);
    }

    /*
    Formats an improper fraction by calling on formatFraction()
    Pre: takes in the instance variables of numerator and denominator
    Post: graphically formats them in a 2D String array from formatFraction()
    */
	public String[] formatImproper(){
        if(wholeNeg || mixedNeg){
            //formats correctly if there was a negative found in mixedToImproper
            numerator = -numerator;
            mixedNumerator = -mixedNumerator;
        }

        if(denomNeg){
            denominator = -denominator;
        }

        return formatFraction(numerator, denominator);
    }

    /*
    Takes in a decimal, then converts it into fraction
    Pre: Takes in the String decimal, which is the user input, with an expected '.' to represent the decimal
    Post: modifies the Fraction object so that the numerator and denominator are reflective of the the respective decimal
    */
    public void decimalToFrac(String decimal){

        boolean negDecimal = false;

        if(decimal.charAt(0) == '-'){ //checks if the decimal is negative 
            negDecimal = true;
            decimal = decimal.substring(1,decimal.length()); 
            //modifies the length of the String so that it is only looking at the absolute value
        }

        for(int i = 0; i<decimal.length(); i++){
            if(decimal.charAt(i)== '.'){
                int numberOfDecimals = decimal.length()-decimal.indexOf(".")-1; //calculates number of decimal places after the decimal
                decimal = decimal.substring(0, decimal.indexOf(".")) + decimal.substring(decimal.indexOf(".")+1, decimal.length());
                denominator = (long)Math.pow(10,numberOfDecimals); //denominator has as many zeros as there are decimal places 
            }
        }
        numerator = parseLong(decimal); //parses the String to long data type 

        if(negDecimal){ //accounts for if decimal inputted was initially negative
            numerator = -numerator;
        }
        simplify();
        //System.out.println(numerator + "/" + denominator);
    }

    /*
    Calculates a decimal by doing real division on the numerator and the denominator
    Pre: Takes in the instance variables denominator and numerator
    Post: returns the double, which is the corresponding decimal
    */
    public double calcDecimal(){
        // if(wholeNumber > 0){
        //     mixedToImproper(); //if the fraction is mixed it will convert it into improper before it goes converted to decimal
        // }

        // if(wholeNeg || mixedNeg){ 
        //     return -(Math.abs(wholeNumber) + Math.abs((double)numerator/(double)denominator)); 
        // }

        return (double)numerator/(double)denominator;   
        // return Math.abs(wholeNumber) + Math.abs((double)numerator/(double)denominator);   
    }

    /*
    Parses a String into a long data type so the resulting value can be used in a Fraction object
    Pre: Takes in a user inputted String
    Post: returns a long data type, which is is the "long casted" respective String
    */
    public static long parseLong(String s) { 
        //takes in a String and converts it into a long 
        long num = 0;
        int location = 1;
        for (int i = s.length()-1; i >= 0; i--) {
            num += (s.charAt(i) - '0') * location;
            location *= 10;
        }
        return num;
    }
}