# TheLongDecimalClass
# CS 1110 profs, Spring 2016
# May 2016

""" Contains a class that supports long
decimal addition and multiplication.
"""

from TheLongIntClass import *

class LongDecimal(object):
    """
    Attributes:
        Whole: the whole number part  [LongInt]
        Fract: the fraction part  [LongInt w/out trailing zeroes ]

        UPDATE (Mon May 9): Fract's String should always have length >= 1,
        and should contain no extra trailing zeroes.  For example, '0' is allowed;
        "", None, and '04000' are not allowed.
    """
    def __init__(self,x):
        """
        PreC: x can be either
           a string that is comprised of digits and at most one
                decimal point.
        or
           a valid nonnegative int or float literal, i.e., 37 , .37 , 37.
        or
           a reference to a LongDecimal object.

        UPDATES:for the purposes of this assignment, assume input is not
        in scientific notation.
        """
        if isinstance(x,LongDecimal):
            self.Whole = LongInt(x.Whole.getString())
            self.Fract = LongInt(x.Fract.getString())
            self.fix_trailing()
            return

        if isinstance(x,float) or isinstance(x,int):
            x = str(x)
        k = x.find('.')
        if k==-1 or k==len(x)-1:
            self.Whole = LongInt(x)
            self.Fract = LongInt('0')
        else:
            self.Whole = LongInt(x[:k])
            self.Fract = LongInt(x[k+1:])
        self.fix_trailing()

    def getWhole(self):
        """Returns Whole as a string"""
        return self.Whole.getString()

    def getFract(self):
        """Returns Fract as a string"""
        return self.Fract.getString()

    def __str__(self):
        return self.getWhole()+'.'+ self.getFract()

    def __add__(self,other):
        """ Returns the sum of self and other as a LongDecimal
        PreC: other is a reference to a LongDecimal or LongInt object.
        """
        if isinstance(other,LongDecimal):
            return self.Add(other)
        else:
            A = LongDecimal(other.getString())
            return self.Add(A)

    # helper method
    # Makes use of times10 from LongInt
    def Add(self, other):
        """ Returns the sum of self and other as a LongDecimal
        PreC: other is a reference to a LongDecimal .
        """
        nAFrac = len(self.getFract()) # number of digits in fractional part
        nBFrac = len(other.getFract())
        A = LongInt(self.getWhole()+self.getFract()) # a string without a decimal point
        B = LongInt(other.getWhole()+other.getFract())

        # get A and B's Fracts to line up.
        if nAFrac < nBFrac:
            A.times10(nBFrac-nAFrac)
        else:
            B.times10(nAFrac-nBFrac)
        sum_string = (A+B).getString()

        if nAFrac < nBFrac:
            dec_place = len(sum_string) - nBFrac
        else:
            dec_place = len(sum_string) - nAFrac
        adjusted_string = (sum_string[:dec_place]
                            + '.'
                            + sum_string[dec_place:])

        return LongDecimal(adjusted_string).fix_trailing()


    # helper method - alternate implementation w/out using
    # times10 method of LongInt, uses helper function
    def Add2(self,other):
        """ Returns the sum of self and other as a LongDecimal
        PreC: other is a reference to a LongDecimal .
        """

        # Idea: use LongInt addition, working around the fact
        # that LongInts "don't have decimal points" in them.

        # Suppose that "x_shorter" is the LongDecimal with fewer digits
        # in its Fract, and "x_longer" is the one with more digits in
        # its Fract. Example: x_shorter is 1234.56, x_longer is 29.2983 .
        # (a) 29.2983 ->    "292983", a string "longer" with no decimal
        # (b) 1234.56 ->  "12345600", a strong "shorter" with extra 0s
        # (c) add corresponding longints:
        #                  12638583
        # (d) get the string "12638583" and make it a LongDecimal
        # with number of digits in its Fract same as for longer:
        #     Whole: 1263 Fract: 8583

        nAFrac = len(self.getFract()) # number of digits in fractional part
        nBFrac = len(other.getFract())
        A = self.getWhole()+self.getFract() # a string without a decimal point
        B = other.getWhole()+other.getFract()
        if nAFrac<nBFrac:
            # (nBFrac-nAFrac)*'0' is the number of zeroes needed to "pad" A
            # so that the fractional parts have the same length.
            # Example: for 1234.56 + 29.29837, we add 3 '0's to '123456':
            # 123456000 + 2929837 = 126385837
            return AddLongerToShorter(B, A+(nBFrac-nAFrac)*'0', nBFrac)
        else:
            return AddLongerToShorter(A, B+(nAFrac-nBFrac)*'0', nAFrac)




    def __mul__(self,other):
        """ Returns the product of self and other as a LongDecimal
        PreC: other is a reference to a LongDecimal or LongInt object.
        """
        if isinstance(other,LongDecimal):
            return self.Mult(other)
        else:
            A = LongDecimal(other.getString())
            return self.Mult(A)

    # helper method
    def Mult(self,other):
        """ Returns the product of self and other as a LongDecimal
        PreC: other is a reference to a LongDecimal .
        Note: calls fix_trailing for the result
        """
        nA  = len(self.getFract())
        nB = len(other.getFract())
        A =  self.getWhole() + self.getFract()
        B =  other.getWhole() + other.getFract()
        C = LongInt(A)*LongInt(B)
        s = C.getString()
        k = len(s)-(nA+nB)
        return LongDecimal(s[:k]+'.'+s[k:]).fix_trailing()

    # helper method
    def fix_trailing(self):
        """Remove trailing zeroes from Fract of LongDecimal ld.
        If removing all the trailing zeroes would leave an empty string,
        set the Frac String to "0".
        Returns self (as a convenience to the caller).
        PreC: self.Fract.String is a string."""
        self.Fract.String = self.Fract.String.rstrip('0')
        if len(self.Fract.String) == 0:
            self.Fract.String = "0"
        return self


# helper function for Add2 above
def AddLongerToShorter(longer, short, n_long):
    """Helper function for addition.  Not for general use.
     Helper for Add2. longer = (a); short = (b).
     See comments for Add2 method.
     Note: calls fix_trailing method for the result"""

    # part (c)
    sum = LongInt(short) + LongInt(longer)

    # part (d)
    sum_string = sum.getString()
    dec_place = len(sum_string) - n_long
    return LongDecimal(sum_string[:dec_place] + '.' + sum_string[dec_place:]).fix_trailing()


def main():

    print 'section 2.3 output'
    x = LongDecimal(1234.56789)
    y = LongDecimal('398948937.3389493374')
    z = x*y
    print z
    x = LongDecimal(111.1111)
    y = LongDecimal(22.22)
    z = x+y
    print z
    x = LongDecimal(12345.56789)
    y = LongInt(1000)
    z = x+y
    print z
    x = LongDecimal('123456789.123456789')
    y = LongInt(1000)
    z = x*y
    print z

    y = LongDecimal(111.1111)
    x = LongDecimal(22.22)
    z = x+y
    print z

    #x = raw_input('x: ')
    #y = raw_input('y: ')
    x = '123456789.123456789'
    y = 1000

    xLD = LongDecimal(x)
    yLD = LongDecimal(y)
    zLD = LongDecimal(y)

    print '\nFirst round'
    print xLD
    print yLD
    print zLD

    print '\nSecond round'
    print float(x)+float(y)
    sumLD  = xLD + yLD
    print sumLD

    print '\nThird round'
    print float(x)*float(y)
    prodLD = xLD*yLD
    print prodLD


    print '\nFourth round'
    d = 12343726
    dLI = LongInt(d)
    z = xLD + dLI
    print z
    print float(x)+d
    z = xLD*dLI
    print z
    print float(x)*d




if __name__ == '__main__':
    main()