def factorial(argument: int) -> int: """Returns the product of all non-negative integers less than or equal to some non-negative integer. Args: argument (int): Non-negative integer used as input to factorial function Returns: int: Product of all non-negative integers less than or equal to 'argument' Raises: InputError: If 'argument' is not a non-negative integer """ # Ensure that input is an integer. if int(argument) != argument: raise exceptions.InputError( argument, "Input to factorial must be a positive integer.") argument = int(argument) # Ensure that input is non-negative. if argument < 0: raise exceptions.InputError( argument, "Input to factorial must be a positive integer.") # Calculate factorial using repeated multiplication. result = 1 for i in range(1, argument + 1): result *= i return result
def decimal_to_binary_fraction(value: float) -> str: """Returns a string that contains the binary equivalent of a decimal fraction. Args: value (float): Value in decimal Returns: str: Value in binary Raises: InputError: If input is not a valid positive number. """ if common.is_negative(value): raise exceptions.InputError(value, "decimal_to_binary_fraction does not accept negative inputs.") bits = list() while value != 0 and (len(bits) <= 15): new_value = float(value * 2) number_to_store = int(new_value) value = new_value % 1 bits.append(number_to_store) binary_string = "0." for x in bits: binary_string = binary_string + str(x) return binary_string
def decimal_to_binary_integer(value: int) -> str: """Returns a string that contains the binary equivalent of a decimal integer. Args: value (int): Value in decimal Returns: str: Value in binary Raises: InputError: If input is not a valid positive number. """ if common.is_negative(value): raise exceptions.InputError(value, "decimal_to_binary_integer does not accept negative inputs.") bits = list() if value == 0: return "" while value != 0: number_to_store = value % 2 value = value // 2 bits.append(number_to_store) binary_string = "" for x in reversed(bits): binary_string = binary_string + str(x) return binary_string
def ln_taylor(argument: float, term_count: int = 50) -> float: """Returns an approximation of the value of the natural logarithm of some argument using the Maclaurin series expansion of ln(1-x). Maclaurin series used only converges when 'argument' is a real value between 0 and 2. Args: argument (float): Argument to natural logarithm being approximated term_count (int): Number of terms used in approximation of natural logarithm Returns: float: Approximation of natural logarithm of 'argument' Raises: InputError: If 'argument' is not within interval of convergence for Taylor series used. """ # Ensure that series will converge for argument provided. if argument <= 0 or argument >= 2: raise exceptions.InputError( argument, "This expansion of natural logarithms only converges for arguments between 0 and 2." ) # Perform calculation. x = 1 - argument result = 0 numerator = 1 for denominator in range(1, term_count): numerator *= x result -= numerator / denominator return result
def pow(base: float, exponent: float, root_used: int = int(1E10)) -> float: """Returns an approximation of some power with base 'base' and exponent 'exponent'. Function implementation does not accept powers with negative bases and real exponents. Args: base (float): Base of power being approximated exponent (float): Exponent of power being approximated root_used (float): Root used to approximate real part of exponent. Larger roots yield greater accuracy but risk overflow problems for large bases. Returns: float: Approximation of power with base 'base' and exponent 'exponent' Raises: InputError: If 'base' is negative and 'exponent' is not an integer. """ # Get integer and decimal parts of exponent. integer_part_of_exponent = int(exponent) fractional_part_of_exponent = exponent - integer_part_of_exponent # Ensure that base is not negative if exponent is not a integer. if common.is_negative(base) and fractional_part_of_exponent != 0: raise exceptions.InputError(None, "Negative base with fractional exponent.") # Calculate integer part using exponentiation by squaring result = pow_int(float(base), integer_part_of_exponent) # If fractional part remains, approximate it using Newton's method for the denominator and exponentiation by # squaring for the numerator if fractional_part_of_exponent != 0: result *= pow_int(radical(float(base), root_used), int(root_used * fractional_part_of_exponent)) return result
def binary_to_decimal_fraction(value: str) -> float: """Returns a float that contains the decimal equivalent of a binary fraction. Args: value (str): Value in binary Returns: float: Value in decimal Raises: InputError: If input is not a valid positive binary number. """ if value[0] == '-': raise exceptions.InputError(value, "binary_to_decimal_fraction does not accept negative inputs.") if not is_binary(value): raise exceptions.InputError(value, "Not in binary.") the_number = str(value)[2:] position = -1 value = 0 for char in the_number: value += int(char) * exponents.pow(2,position) position -= 1 return value
def binary_to_decimal_integer(value: str) -> int: """Returns an integer that contains the decimal equivalent of a binary integer. Args: value (str): Value in binary Returns: int: Value in decimal Raises: InputError: If input is not a valid positive binary number. """ if value[0] == '-': raise exceptions.InputError(value, "binary_to_decimal_integer does not accept negative inputs.") if not is_binary(value): raise exceptions.InputError(value, "Not in binary.") the_number = str(value) position = 0 value = 0 for char in reversed(the_number): value += int(char) * exponents.pow(2,position) position += 1 return value
def log(argument: float, base: float = 10) -> float: """Returns an approximation of the value of some logarithm with base 'base' and argument 'argument'. Args: argument (float): Argument to logarithm being approximated base (float): Base of logarithm being approximated Returns: float: Approximation of value of logarithm of with base 'base' and argument 'argument' Raises: InputError: If 'argument' and/or 'base' are outside of domain of logarithm. """ # Ensure that base is not equal to 1. if base == 1: raise exceptions.InputError(base, "Logarithm with base of 1 is undefined.") # Ensure that base is positive. if not common.is_positive(base): raise exceptions.InputError( base, "Logarithm with non-positive base is undefined.") # Calculate logarithm with arbitrary base using conversion of base property of logarithms. return ln(argument) / ln(base)
def inverse(argument: float) -> float: """Returns inverse of input. Args: argument (float): Non-zero input to inverse function Returns: float: Inverse of 'argument'. Raises: InputError: If 'argument' is equal to 0 """ # Ensure that input is not equal to 0. if argument == 0: raise exceptions.InputError(argument, "Inverse of 0 is undefined.") # Calculate inverse of argument. return 1 / argument
def is_odd(argument: int) -> bool: """Returns true is input is odd and false otherwise. Args: argument (int): Value to be checked Returns: bool: True if 'argument' is odd and false otherwise Raises: InputError: If 'argument' is not an integer """ # Ensure that argument is an integer. if not isinstance(argument, int): raise exceptions.InputError(argument, "Parity of non-integers is undefined.") return argument & 1
def ln(argument: float) -> float: """Returns an approximation of the value of the natural logarithm of some argument. Args: argument (float): Argument to natural logarithm being approximated Returns: float: Approximation of natural logarithm of 'argument' Raises: InputError: If 'argument' is not within domain of natural logarithm (set of positive real numbers). """ # Ensure that argument is within domain of natural logarithm. if not common.is_positive(argument): raise exceptions.InputError(argument, "Not in domain of log function.") # We use frexp to fetch mantissa and exponent of argument in a platform agnostic way. mantissa, exponent = math.frexp(argument) # It follows from the properties of logarithms that ln(m*2^p)=ln(m)+p*ln(2). ln_2 = 0.6931471805599453 ln_mantissa = ln_taylor(mantissa) return ln_mantissa + exponent * ln_2
def pow_int(base: float, exponent: int) -> float: """Returns value of 'base' to the power of 'exponent' using exponentiation by squaring where the latter is an integer. Args: base (float): Base of power to be returned exponent (int): Exponent of power to be returned Returns: float: Value of 'base' to the power of 'exponent' Raises: InputError: If 'exponent' is not an integer """ # Ensure that exponent is an integer. if not isinstance(exponent, int): raise exceptions.InputError( exponent, "Exponentiation by squaring does not work with non-integer exponents." ) # If exponent is negative, use the property x^y = (1/x)^-y. if common.is_negative(exponent): base = common.inverse(base) exponent *= -1 # Iterate over bits in exponent result = 1 while common.is_positive(exponent): # If exponent is odd, we can use the fact that x^y = x(x^2)^((n-1)/2) if common.is_odd(exponent): result *= base # Normally, we'd subtract 1 from y here but we don't actually need to since it will be lost in the coming bit-shift. # Now that y is even, we can use the fact that x^y = (x^2)^(n/2) when y is even base *= base exponent >>= 1 return result
def radical(radicand: float, index: int, delta: float = 1E-10) -> float: """Returns an approximation of the real value of some radical using Newton's method for finding roots of functions if such a value exists. Value returned is positive if a positive radical exists and negative otherwise. If no real root exists, an exception is raised. Args: radicand (float): Radicand of radical to be determined index (int): Index of radical to be determined delta (float): Error tolerance Returns: float: Approximation of radical defined by arguments Raises: InputError: If no real root exists for provided arguments """ # Ensure that real value of root exists. if common.is_negative(radicand) and common.is_even(index): raise exceptions.InputError(None, "Not in domain of radical function.") # Approximate radicand using Newton's method. approx = 1.0 old_mantissa, old_exp = math.frexp(approx) while True: # Incrementally improve approximation. power = pow_int(approx, index) approx = (index - 1 + radicand / power) * (approx / index) # Return when approximation stops improving crrt_mantissa, crrt_exp = math.frexp(approx) diff = abs(old_mantissa - crrt_mantissa) # We use 1E-15 here because a double precision floating point mantissa can hold no more than 52 bits of precision. if crrt_exp == old_exp and diff < 1E-15: return approx old_mantissa = crrt_mantissa old_exp = crrt_exp