def __add__(lhs: IntegerNumber, rhs: IntegerNumber) -> IntegerNumber: log(f'Adding {lhs} and {rhs}') log_indent() def determine_sign(magnitude: WholeNumber, default_sign: Sign) -> Sign: if magnitude == WholeNumber.from_int(0): return None else: return default_sign if lhs.sign is None: # sign of None is only when magnitude is 0, 0 + a = a sign = rhs.sign magnitude = rhs.magnitude elif rhs.sign is None: # sign of None is only when magnitude is 0, a + 0 = a sign = lhs.sign magnitude = lhs.magnitude elif lhs.sign == rhs.sign: magnitude = lhs.magnitude + rhs.magnitude sign = determine_sign(magnitude, lhs.sign) elif lhs.sign != rhs.sign: if rhs.magnitude >= lhs.magnitude: magnitude = rhs.magnitude - lhs.magnitude sign = determine_sign(magnitude, rhs.sign) else: magnitude = lhs.magnitude - rhs.magnitude sign = determine_sign(magnitude, lhs.sign) log_unindent() log(f'sign: {sign}, magnitude: {magnitude}') return IntegerNumber(sign, magnitude)
def __mul__(lhs: IntegerNumber, rhs: IntegerNumber) -> IntegerNumber: log(f'Multiplying {lhs} and {rhs}') log_indent() def determine_sign(magnitude: WholeNumber, default_sign: Sign) -> Sign: if magnitude == WholeNumber.from_int(0): return None else: return default_sign if lhs.sign is None: # when sign isn't set, magnitude is always 0 -- 0 * a = 0 sign = None magnitude = WholeNumber.from_int(0) elif rhs.sign is None: # when sign isn't set, magnitude is always 0 -- a * 0 = 0 sign = None magnitude = WholeNumber.from_int(0) elif (lhs.sign == Sign.POSITIVE and rhs.sign == Sign.POSITIVE) \ or (lhs.sign == Sign.NEGATIVE and rhs.sign == Sign.NEGATIVE): magnitude = lhs.magnitude * rhs.magnitude sign = determine_sign(magnitude, Sign.POSITIVE) elif (lhs.sign == Sign.POSITIVE and rhs.sign == Sign.NEGATIVE) \ or (lhs.sign == Sign.NEGATIVE and rhs.sign == Sign.POSITIVE): magnitude = lhs.magnitude * rhs.magnitude sign = determine_sign(magnitude, Sign.NEGATIVE) log_unindent() log(f'sign: {sign}, magnitude: {magnitude}') return IntegerNumber(sign, magnitude)
def reciprocal(self: FractionNumber) -> FractionNumber: # Sign is on the numerator log(f'Getting reciprocal of {self}') res = FractionNumber(self._denominator, self._numerator) log(f'Result: {res}') return res
def __truediv__(lhs: FractionNumber, rhs: FractionNumber) -> FractionNumber: # Sign is only kept on the numerator, not the denominator log(f'Dividing {lhs} and {rhs}') res = FractionNumber(lhs._numerator * rhs._denominator, lhs._denominator * rhs._numerator) log(f'Result: {res}') return res
def to_words(self: IntegerNumber) -> str: log(f'Converting {self}...') output = '' if self.sign == Sign.NEGATIVE: output += 'negative ' output += self.magnitude.to_words() log_unindent() log(f'{output}') return output.lstrip()
def __eq__(lhs: FractionalNumber, rhs: FractionalNumber) -> bool: if not isinstance(rhs, FractionalNumber): raise Exception() log(f'Equality testing {lhs} and {rhs}...') log_indent() ret = lhs.digits == rhs.digits log_unindent() log(f'{ret}') return ret
def to_words(self: FractionNumber) -> str: log(f'Converting {self}...') output = '' if self.sign == Sign.NEGATIVE: output += 'negative ' output += self.numerator.to_words() output += ' over ' output += self.denominator.to_words() log_unindent() log(f'{output}') return output.lstrip()
def __add__(lhs: FractionNumber, rhs: FractionNumber) -> FractionNumber: # Sign is only kept on the numerator, not the denominator log(f'Adding {lhs} and {rhs}...') log_indent() log(f'Converting {lhs} and {rhs} to equivalent fractions with least common denominator...' ) lhs, rhs = FractionNumber.common_denominator_lcm(lhs, rhs) log(f'Equivalent fractions: {lhs} and {rhs}') log(f'Adding numerators of {lhs} and {rhs}...') res = FractionNumber(lhs._numerator + rhs._numerator, lhs._denominator) log_unindent() log(f'Result: {res}') return res
def factor_fast(num: WholeNumber) -> Set[WholeNumber]: log(f'Factoring {num}...') log_indent() factors: Set[WholeNumber] = set() for factor1 in WholeNumber.range(WholeNumber.from_int(1), num, end_inclusive=True): log(f'Test if {factor1} is a factor...') factor2, remainder = num / factor1 if remainder == WholeNumber.from_int(0): factors.add(factor1) factors.add(factor2) log(f'Yes: ({factor1} and {factor2} are factors)') else: log(f'No') log_unindent() log(f'{factors}') return factors
def choose_start_modifier_for_divte( start_test_num: DecimalNumber) -> DecimalNumber: log(f'Choosing a starting modifier for {start_test_num}...') log_indent() log(f'{start_test_num} has {len(start_test_num.whole.digits)} digits') num_of_zeros = len(start_test_num.whole.digits) - 1 start_modifier_num = DecimalNumber.from_str('1' + '0' * num_of_zeros) log(f'Starting modifier: {start_modifier_num}') log_unindent() log(f'{start_modifier_num}') return start_modifier_num
def __truediv__(lhs: IntegerNumber, rhs: IntegerNumber) -> (IntegerNumber, IntegerNumber): log(f'Dividing {lhs} and {rhs}') log_indent() def determine_sign(magnitude: WholeNumber, default_sign: Sign) -> Sign: if magnitude == WholeNumber.from_int(0): return None else: return default_sign if lhs.sign is None: # when sign isn't set, magnitude is always 0 -- 0 / a = 0 (quotient_magnitude, remainder_magnitude) = lhs.magnitude / rhs.magnitude quotient_sign = None remainder_sign = None elif rhs.sign is None: # when sign isn't set, magnitude is always 0 -- a / 0 = err raise Exception('Cannot divide by 0') elif (lhs.sign == Sign.POSITIVE and rhs.sign == Sign.POSITIVE) \ or (lhs.sign == Sign.NEGATIVE and rhs.sign == Sign.NEGATIVE): (quotient_magnitude, remainder_magnitude) = lhs.magnitude / rhs.magnitude quotient_sign = determine_sign(quotient_magnitude, Sign.POSITIVE) remainder_sign = determine_sign(remainder_magnitude, Sign.POSITIVE) elif (lhs.sign == Sign.POSITIVE and rhs.sign == Sign.NEGATIVE) \ or (lhs.sign == Sign.NEGATIVE and rhs.sign == Sign.POSITIVE): (quotient_magnitude, remainder_magnitude) = lhs.magnitude / rhs.magnitude quotient_sign = determine_sign(quotient_magnitude, Sign.NEGATIVE) remainder_sign = determine_sign(remainder_magnitude, Sign.NEGATIVE) log_unindent() log(f'QUOTIENT: sign: {quotient_sign}, magnitude: {quotient_magnitude}') log(f'REMAINDER: sign: {remainder_sign}, magnitude: {remainder_magnitude}') return IntegerNumber(quotient_sign, quotient_magnitude), IntegerNumber(remainder_sign, remainder_magnitude)
def factor_naive(num: WholeNumber) -> Set[WholeNumber]: log(f'Factoring {num}...') log_indent() factors: Set[WholeNumber] = set() for factor1 in WholeNumber.range(WholeNumber.from_int(1), num, end_inclusive=True): for factor2 in WholeNumber.range(WholeNumber.from_int(1), num, end_inclusive=True): log(f'Testing if {factor1} and {factor2} are factors...') if factor1 * factor2 == num: factors.add(factor1) factors.add(factor2) log(f'Yes') else: log(f'No') log_unindent() log(f'{factors}') return factors
def gcd_euclid(num1: WholeNumber, num2: WholeNumber) -> WholeNumber: log(f'Calculating gcd for {num1} and {num2}...') log_indent() next_nums = [num1, num2] while True: log(f'Sorting {next_nums}...') next_nums.sort() # sort smallest to largest next_nums.reverse() # reverse it so that it's largest to largest log(f'Checking if finished ({next_nums[1]} == 0?)...') if next_nums[1] == WholeNumber.from_int(0): found = next_nums[0] break log(f'Dividing {next_nums} and grabbing the remainder for the next test...' ) _, remainder = next_nums[0] / next_nums[1] next_nums = [next_nums[1], remainder] log_unindent() log(f'GCD is {found}') return found
def __sub__(lhs: IntegerNumber, rhs: IntegerNumber) -> IntegerNumber: log(f'Subtracting {lhs} and {rhs}') log_indent() def determine_sign(magnitude: WholeNumber, default_sign: Sign) -> Sign: if magnitude == WholeNumber.from_int(0): return None else: return default_sign def flip_sign(sign: Sign) -> Sign: if sign == Sign.POSITIVE: return Sign.NEGATIVE elif sign == Sign.NEGATIVE: return Sign.POSITIVE if lhs.sign is None: # sign of None is only when magnitude is 0, 0 - a = -a sign = flip_sign(rhs.sign) magnitude = rhs.magnitude elif rhs.sign is None: # sign of None is only when magnitude is 0, a - 0 = a sign = lhs.sign magnitude = lhs.magnitude elif lhs.sign == rhs.sign: if rhs.magnitude >= lhs.magnitude: magnitude = rhs.magnitude - lhs.magnitude sign = determine_sign(magnitude, flip_sign(lhs.sign)) else: magnitude = lhs.magnitude - rhs.magnitude sign = determine_sign(magnitude, lhs.sign) elif lhs.sign != rhs.sign: magnitude = lhs.magnitude + rhs.magnitude sign = determine_sign(magnitude, lhs.sign) log_unindent() log(f'sign: {sign}, magnitude: {magnitude}') return IntegerNumber(sign, magnitude)
def __mul__(lhs: FractionNumber, rhs: FractionNumber) -> FractionNumber: # Sign is only kept on the numerator, not the denominator log(f'Multiplying {lhs} and {rhs}') log_indent() log(f'Multiplying numerators {lhs._numerator} and {rhs._numerator}...') numerator = lhs._numerator * rhs._numerator log(f'Multiplying denominators {lhs._denominator} and {rhs._denominator}...' ) denominator = lhs._denominator * rhs._denominator res = FractionNumber(numerator, denominator) log_unindent() log(f'Result: {res}') return res
def is_prime(num: WholeNumber) -> bool: log(f'Test if {num} is prime...') log_indent() num_factors = factor_fastest(num) # At a minimum, all counting numbers have the factors 1 and the number itself (2 factors). If # there are more factore than that, it's a composite. Otherwise, it's a primse. log_unindent() if len(num_factors) == 2: log(f'{num}\'s factors are {num_factors} -- it is a prime') return True else: log(f'{num}\'s factors are {num_factors} -- it is a composite') return False
def factor_tree(num: WholeNumber) -> FactorTreeNode: log(f'Creating factor tree for {num}...') factors = factor_fastest(num) # remove factor pairs that can't used in factor true: (1, num) or (num, 1) factors = set( [f for f in factors if f != WholeNumber.from_int(1) and f != num]) ret = FactorTreeNode() if len(factors) == 0: ret.value = num log(f'Cannot factor {num} is prime -- resulting tree: {ret}') else: factor1 = next(iter(factors)) factor2, _ = num / factor1 ret.value = num ret.left = factor_tree(factor1) ret.right = factor_tree(factor2) log(f'Factored {num} to {factor1} and {factor2} -- resulting tree: {ret}' ) return ret
def round(self: DecimalNumber, position: str) -> DecimalNumber: log(f'Rounding {self} at {position} position...') log_indent() position = position.strip() if position.endswith('s'): position = position[:-1] position_word_to_index = { 'hundred-quintillion': 20, 'ten-quintillion': 19, 'quintillion': 18, 'hundred-quadillion': 17, 'ten-quadrillion': 16, 'quadrillion': 15, 'hundred-trillion': 14, 'ten-trillion': 13, 'trillion': 12, 'hundred-billion': 11, 'ten-billion': 10, 'billion': 9, 'hundred-million': 8, 'ten-million': 7, 'million': 6, 'hundred-thousand': 5, 'ten-thousand': 4, 'thousand': 3, 'hundred': 2, 'ten': 1, 'one': 0, 'tenth': -1, 'hundredth': -2, 'thousandth': -3, 'ten-thousandth': -4, 'hundred-thousandth': -5, 'millionth': -6, 'ten-millionth': -7, 'hundred-millionth': -8, 'billionth': -9, 'ten-billionth': -10, 'hundred-billionth': -11, 'trillionth': -12, 'ten-trillionth': -13, 'hundred-trillionth': -14, 'quadrillionth': -15, 'ten-quadrillionth': -16, 'hundred-quadillionth': -17, 'quintillionth': -18, 'ten-quintillionth': -19, 'hundred-quintillionth': -20, } position_idx = position_word_to_index[position] if position_idx is None: raise Exception('Position unknown') next_position_idx = position_idx - 1 log(f'Determining adder based on following position...') log_indent() log(f'Checking if digit at following position is >= 5...') following_digit = WholeNumber.from_digit(self[next_position_idx]) if following_digit >= WholeNumber.from_str("5"): log(f'True ({following_digit} >= 5), deriving adder based on position...' ) if position_idx >= 0: adder = DecimalNumber( self.sign, WholeNumber.from_str('1' + '0' * position_idx), FractionalNumber.from_str('0')) else: adder = DecimalNumber( self.sign, WholeNumber.from_str('0'), FractionalNumber.from_str('0' * -(position_idx + 1) + '1')) else: log(f'False ({following_digit} < 5), setting adder to 0...') adder = DecimalNumber.from_str('0') log_unindent() log(f'{adder}') log(f'Adding {adder} to {self}...') ret = self.copy() + adder log(f'{ret}') log(f'Truncating all following positions...') log_indent() if position_idx >= 0: for i in range(0, position_idx): ret[i] = Digit(0) log(f'{ret}') for i in range(0, len(self.fractional.digits)): ret[-i - 1] = Digit(0) log(f'{ret}') else: for i in range(-position_idx, len(self.fractional.digits)): ret[-i - 1] = Digit(0) log(f'{ret}') log_unindent() log(f'{ret}') log_unindent() log(f'{ret}') return ret
def ladder(num: WholeNumber) -> Set[WholeNumber]: prime_factors: List[WholeNumber] = [] log(f'Testing primes (using ladder method) to see which is factor of {num}...' ) log_indent() while not is_prime(num): prime_to_test = WholeNumber.from_int(2) while True: log(f'Testing if {prime_to_test} is divisible by {num}...') (new_num, remainder) = num / prime_to_test if remainder == WholeNumber.from_int(0): break prime_to_test = calculate_next_prime(prime_to_test) log(f'Found! {prime_to_test} is a prime factor -- {new_num} * {prime_to_test} = {num}' ) prime_factors.append(prime_to_test) num = new_num log(f'Testing primes to see which is factor of {num}...') log(f'{num} itself is a prime!') prime_factors.append(num) log_unindent() log(f'Prime factors: {prime_factors}') return prime_factors
def trial_and_error_div(dividend: DecimalNumber, divisor: DecimalNumber) -> DecimalNumber: log(f'Dividing {dividend} and {divisor}...') log_indent() log(f'Ensuring {dividend} / {divisor} results in a terminating decimal...' ) if not DecimalNumber.will_division_terminate(dividend, divisor): raise Exception('Resulting decimal will be non-terminating') log(f'Treating {dividend} and {divisor} as non-negative to perform the algorithm...' ) orig_dividend_sign = dividend.sign orig_divisor_sign = divisor.sign if dividend.sign == Sign.NEGATIVE: dividend *= DecimalNumber.from_str('-1.0') if divisor.sign == Sign.NEGATIVE: divisor *= DecimalNumber.from_str('-1.0') log(f'Non-negative: {dividend} and {divisor}...') log(f'Calculating starting test number...') test = DecimalNumber.choose_start_test_num_for_divte(divisor, dividend) log(f'{test}') log(f'Calculating starting modifier for test number...') modifier = DecimalNumber.choose_start_modifier_for_divte(test) log(f'{modifier}') while True: log(f'Testing {test}: {test} * {divisor}...') test_res = test * divisor log(f'{test_res}') log(f'Is {test_res} ==, >, or < to {dividend}? ...') log_indent() try: if test_res == dividend: log(f'{test_res} == {dividend} -- Found') break elif test_res > dividend: log(f'{test_res} > {dividend} -- Decrementing {test} by {modifier} until not >...' ) log_indent() while True: log(f'Decrementing {test} by {modifier}...') test -= modifier log(f'{test} * {divisor}...') modify_res = test * divisor log(f'{modify_res}') if not modify_res > dividend: break log_unindent() log(f'Done: {test}') elif test_res < dividend: log(f'{test_res} < {dividend} -- Incrementing {test} by {modifier} until not <...' ) log_indent() while True: log(f'Incrementing {test} by {modifier}...') test += modifier log(f'{test} * {divisor}...') modify_res = test * divisor log(f'{modify_res}') if not modify_res < dividend: break log_unindent() log(f'Done: {test}') finally: log_unindent() log(f'Calculating position for next test...') modifier *= DecimalNumber.from_str('0.1') log(f'{modifier}') log_unindent() log(f'{test}') log(f'Modifying sign of {test} based on original sign of dividend ({orig_dividend_sign}) and divisor ({orig_divisor_sign})...' ) if orig_dividend_sign == Sign.NEGATIVE and orig_divisor_sign != Sign.NEGATIVE \ or orig_dividend_sign != Sign.NEGATIVE and orig_divisor_sign == Sign.NEGATIVE: test *= DecimalNumber.from_str('-1.0') log(f'{test}') return test
def choose_start_test_num_for_divte( input1: DecimalNumber, expected_product: DecimalNumber) -> DecimalNumber: log(f'Choosing a starting number to find {input1} \\* ? = {expected_product}...' ) log_indent() log(f'Checking which case should apply...') if input1 < DecimalNumber.from_str( '1.0') and expected_product >= DecimalNumber.from_str('1.0'): log(f'{input1} has {len(input1.fractional.digits)} fractional digits' ) log(f'{expected_product}\'s has {len(expected_product.whole.digits)} whole digits' ) num_of_zeros = len(expected_product.whole.digits) + len( input1.fractional.digits) - 1 start_test_num = DecimalNumber.from_str('1' + '0' * num_of_zeros) else: log(f'{input1} has {len(input1.whole.digits)} whole digits') log(f'{expected_product}\'s has {len(expected_product.whole.digits)} whole digits' ) num_of_zeros = len(expected_product.whole.digits) - len( input1.whole.digits) start_test_num = DecimalNumber.from_str('1' + '0' * num_of_zeros) log(f'Starting number: {start_test_num}') log_unindent() log(f'{start_test_num}') return start_test_num
def will_division_terminate(lhs: DecimalNumber, rhs: DecimalNumber) -> bool: log(f'Checking if {lhs} / {rhs} results in a non-terminating decimal...' ) log_indent() adjust_len_self = len(lhs.fractional.digits) adjust_len_other = len(rhs.fractional.digits) log(f'Generating mock integer number for {lhs}...') lhs_extra_0s = adjust_len_self - len(lhs.fractional.digits) lhs_combined_digits = lhs.fractional.digits + lhs.whole.digits lhs_combined_digits[0:0] = [Digit(0)] * lhs_extra_0s mock_self = IntegerNumber(lhs.sign, WholeNumber(lhs_combined_digits)) log(f'{mock_self}') log(f'Generating mock integer number for {rhs}...') rhs_extra_0s = adjust_len_other - len(rhs.fractional.digits) rhs_combined_digits = rhs.fractional.digits + rhs.whole.digits rhs_combined_digits[0:0] = [Digit(0)] * rhs_extra_0s mock_other = IntegerNumber(rhs.sign, WholeNumber(rhs_combined_digits)) log(f'{mock_other}') log(f'Generating mock fraction for {lhs} / {rhs}...') mock_fraction = FractionNumber(mock_self, mock_other) log(f'{mock_fraction}') log(f'Simplifying mock fraction...') mock_fraction = mock_fraction.simplify() log(f'{mock_fraction}') log(f'Checking if prime factors of denom ({mock_fraction.denominator}) is {{}}, {{2}}, {{5}}, or {{2,5}}...' ) mock_fraction_denom_prime_factors = set( factor_tree(mock_fraction.denominator).get_prime_factors()) if not ( {WholeNumber.from_str('2'), WholeNumber.from_str('5')} == mock_fraction_denom_prime_factors or {WholeNumber.from_str('2')} == mock_fraction_denom_prime_factors or {WholeNumber.from_str('5')} == mock_fraction_denom_prime_factors or 0 == len(mock_fraction_denom_prime_factors)): ret = False log(f'{ret} -- Won\'t terminate.') else: ret = True log(f'{ret} -- Will terminate.') log_unindent() log(f'{ret}') return ret
def __mul__(lhs: DecimalNumber, rhs: DecimalNumber) -> DecimalNumber: log(f'Multiplying {lhs} and {rhs}...') log_indent() adjust_len_self = len(lhs.fractional.digits) adjust_len_other = len(rhs.fractional.digits) log(f'Generating mock integer number for {lhs}...') lhs_extra_0s = adjust_len_self - len(lhs.fractional.digits) lhs_combined_digits = lhs.fractional.digits + lhs.whole.digits lhs_combined_digits[0:0] = [Digit(0)] * lhs_extra_0s mock_self = IntegerNumber(lhs.sign, WholeNumber(lhs_combined_digits)) log(f'{mock_self}') log(f'Generating mock integer number for {rhs}...') rhs_extra_0s = adjust_len_other - len(rhs.fractional.digits) rhs_combined_digits = rhs.fractional.digits + rhs.whole.digits rhs_combined_digits[0:0] = [Digit(0)] * rhs_extra_0s mock_other = IntegerNumber(rhs.sign, WholeNumber(rhs_combined_digits)) log(f'{mock_other}') log(f'Performing {mock_self} * {mock_other}...') mock_ret = mock_self * mock_other log(f'{mock_ret}') log(f'Unmocking {mock_ret} back to decimal...') unadjust_len = adjust_len_self + adjust_len_other ret_sign = mock_ret.sign ret_fractional_digits = [ mock_ret.magnitude[i] for i in range(0, unadjust_len) ] ret_whole_digits = [ mock_ret.magnitude[i] for i in range(unadjust_len, len(mock_ret.magnitude.digits)) ] ret = DecimalNumber(ret_sign, WholeNumber(ret_whole_digits), FractionalNumber(ret_fractional_digits)) log(f'{ret}') log_unindent() log(f'{ret}') return ret
def from_fraction(value: FractionNumber) -> DecimalNumber: log(f'Converting {value} to a decimal number...') log_indent() log(f'Converting {value} to suitable fraction...') value = DecimalNumber.to_suitable_fraction(value) log(f'{value}') num = value.numerator denom = value.denominator if not str(denom).startswith('1'): raise Exception('Denominator must be power of 10') elif not set(str(denom)[1:]) == set('0') and not set( str(denom)[1:]) == set(): raise Exception('Denominator must be power of 10') log(f'Resolving fraction {value}...') whole, remaining = num / denom log(f'{whole} wholes and {remaining} remaining') log(f'Converting {remaining} of {denom} to fractional value...') num_digits_in_rem = len(remaining.digits) num_0s_in_den = len( denom.digits ) - 1 # starts with 1 followed by 0s, so - 1 to ignore the starting 1 num_0s_to_prepend_to_rem = num_0s_in_den - num_digits_in_rem fractional_digits = remaining.digits[:] # copy digits fractional_digits = fractional_digits + [Digit( 0)] * num_0s_to_prepend_to_rem # this prepending 0s... # you might be confused because the # 0s are being addeed to the end, but # that's how FractionalNumber expects # digits -- in reversed order fractional = FractionalNumber(fractional_digits) log(f'{fractional}') sign = value.sign log_unindent() ret = DecimalNumber(sign, whole, fractional) log(f'Decimal number: {ret}') return ret
def as_fraction(self: DecimalNumber) -> FractionNumber: log(f'Converting {self} to fraction number...') log_indent() log(f'Determining denominator based on length of fractional portion ({self.fractional})...' ) denom = IntegerNumber.from_str('1' + '0' * len(self.fractional.digits)) log(f'{denom}') log(f'Converting fractional portion ({self.fractional} to fraction...') fractional_fraction = FractionNumber( IntegerNumber.from_str(str(self.fractional)), denom) log(f'{fractional_fraction}') log(f'Converting whole portion ({self.whole}) to fraction...') whole_fraction = FractionNumber.from_whole(self.whole) log(f'{whole_fraction}') log(f'Adding ({whole_fraction}) to ({fractional_fraction})...') fraction = whole_fraction + fractional_fraction log(f'{fraction}') log(f'Applying sign of ({self.sign}) to {fraction}...') if self.sign == Sign.NEGATIVE: fraction = fraction * FractionNumber.from_str( "-1/1") # make sign negative log(f'{fraction}') log_unindent() return fraction
def to_words(self: DecimalNumber) -> str: fractional_len_to_suffixes = { 1: 'tenth', 2: 'hundredth', 3: 'thousandth', 4: 'ten-thousandth', 5: 'hundred-thousandth', 6: 'millionth', 7: 'ten-millionth', 8: 'hundred-millionth', 9: 'billionth', 10: 'ten-billionth', 11: 'hundred-billionth', 12: 'trillionth', 13: 'ten-trillionth', 14: 'hundred-trillionth', 15: 'quadrillionth', 16: 'ten-quadrillionth', 17: 'hundred-quadillionth', 18: 'quintillionth', 19: 'ten-quintillionth', 20: 'hundred-quintillionth', } log(f'Converting {self}...') log_indent() log(f'Converting whole portion to words...') whole_words = self.whole.to_words() log(f'Whole as words: {whole_words}') log(f'Converting fractional portion to words...') fractional_words = WholeNumber.from_str(str( self.fractional)).to_words() log(f'fractional as words: {fractional_words}') output = '' if self.whole == WholeNumber.from_str( '0') and self.fractional == FractionalNumber.from_str('0'): output += 'zero' else: if self.sign == Sign.NEGATIVE: output += 'negative ' if self.whole != WholeNumber.from_str('0'): output += whole_words if self.whole != WholeNumber.from_str( '0') and self.fractional != FractionalNumber.from_str('0'): output += ' and ' if self.fractional != FractionalNumber.from_str('0'): output += fractional_words suffix = fractional_len_to_suffixes[len( self.fractional.digits)] if suffix is None: raise Exception('Fractional too large') log(f'Fractional suffix: {suffix}') if self.fractional != FractionalNumber.from_str( '0'): # pluralize suffix if more than 1 suffix += 's' output += ' ' + suffix log_unindent() log(f'{output}') return output.strip()
def gcd_factor(num1: WholeNumber, num2: WholeNumber) -> WholeNumber: log(f'Calculating gcd for {num1} and {num2}...') log_indent() log(f'Calculating factors for {num1}...') factors1 = factor_fastest(num1) log(f'Factors for {num1}: {factors1}') log(f'Calculating factors for {num2}...') factors2 = factor_fastest(num2) log(f'Factors for {num2}: {factors2}') log(f'Finding common factors...') common_factors = factors1 & factors2 # set intersection log(f'Common factors for {num1} and {num2}: {common_factors}') found = max(common_factors) log_unindent() log(f'GCD is {found}') return found
def __eq__(lhs: DecimalNumber, rhs: DecimalNumber) -> bool: class FailedTestException(Exception): pass log(f'Equality testing {lhs} and {rhs}...') log_indent() try: log(f'Testing sign...') sign_eq = lhs.sign == rhs.sign if not sign_eq: raise FailedTestException() log(f'Equal') log(f'Testing whole...') whole_eq = lhs.whole == rhs.whole if not whole_eq: raise FailedTestException() log(f'Equal') log(f'Testing fractional...') fractional_eq = lhs.fractional == rhs.fractional if not fractional_eq: raise FailedTestException() log(f'Equal') ret = True except FailedTestException: log(f'Not equal') ret = False log_unindent() log(f'{ret}') return ret
def gcd_naive(num1: WholeNumber, num2: WholeNumber) -> WholeNumber: log(f'Calculating gcd for {num1} and {num2}...') log_indent() log(f'Sorting to determine smaller input...') min_num = min(num1, num2) log(f'Testing up to smaller input ({min_num})...') log_indent() for i in WholeNumber.range(WholeNumber.from_str('1'), min_num, True): log(f'Testing {i}...') quotient1, remainder1 = num1 / i quotient2, remainder2 = num2 / i if remainder1 == 0 and remainder2 == 0: log(f'{num1} and {num2} are both divisible by {i}...') found = i else: log(f'{num1} and {num2} are NOT both divisible by {i}...') log_unindent() log_unindent() log(f'GCD is {found}') return found
def __gt__(lhs: DecimalNumber, rhs: DecimalNumber) -> bool: class PassedTestException(Exception): pass class FailedTestException(Exception): pass log(f'Greater than testing {lhs} and {rhs}...') log_indent() try: log(f'Testing sign...') sign_gt = lhs.sign == Sign.POSITIVE and (rhs.sign == Sign.NEGATIVE or rhs.sign is None) if sign_gt: raise PassedTestException() sign_eq = lhs.sign == rhs.sign if not sign_eq: raise FailedTestException() log(f'Equal') log(f'Testing whole...') if lhs.sign != Sign.NEGATIVE: whole_gt = lhs.whole > rhs.whole else: whole_gt = lhs.whole < rhs.whole if whole_gt: raise PassedTestException() whole_eq = lhs.whole == rhs.whole if not whole_eq: raise FailedTestException() log(f'Equal') log(f'Testing fractional...') if lhs.sign != Sign.NEGATIVE: fractional_gt = lhs.fractional > rhs.fractional else: fractional_gt = lhs.fractional < rhs.fractional if fractional_gt: raise PassedTestException() fractional_eq = lhs.fractional == rhs.fractional if not fractional_eq: raise FailedTestException() log(f'Equal') ret = False except PassedTestException: log(f'Greater') ret = True except FailedTestException: log(f'Not greater or equal') ret = False log_unindent() log(f'{ret}') return ret