Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #code by IFcoltransG
- #before we start, I'm not sorry for all the nested functions.
- #no explicit loops, div, divmod, modulo, or multiplication
- def multiply(x, y)->"int, float or complex (or a sequence type)":
- #accepts any combination of int, float, and complex
- #can also multiply sequences, including strings, by an integer
- def multiply_real(x, y)->"float or int":
- #multiply_real does some initial checks and ensures numbers end up with the correct sign
- #simplifies multiplying two floats to multiplying ints, then multiplying an int by a float
- #delegates to optimised_multiply when multiplying an int by a float
- #for floats 0->1, error is up to about ±1E-17
- #for floats 0->100, error can go up to ±1E-13
- def optimised_multiply(integer, number)->"float or int":
- data_list = [(integer, number)]
- def bind_multiplication_to_list(data_list)->"function":
- #stores a reference to data_list in the closure of iterative_multiplication
- #not strictly necessary to store it in the closure, because it can be accessed from
- # within the function scope, but neater this way I think
- def iterative_multiplication(data)->"float or int":
- #"Russian Shepard algorithm" on an integer and another number
- #much faster than the old way I used—making a range(integer),
- #mapping a (lambda _: number) onto the range, then taking the sum()
- #based on a while loop that I based on a recursive function I made
- #extracts data from tuple
- #integer will be halved, number will be doubled
- #if integer was odd, number is added to the sum
- #(Russian Peasant algorithm leverages an integer's binary form)
- integer, number = data
- if integer <= 0:
- #integer should not be negative
- return 0
- else:
- #can't bitshift a float, so we double number by adding it to itself
- #can bitshift the integer though. bitshifting rounds down every time
- new_data = (integer >> 1, number + number)
- #modifies the object being mapped while iterating through it.
- #Do not do this in regular code!
- #I'm only doing it to avoid explicit loops and recursion
- data_list.append(new_data)
- #calculates integer % 2 and compares it to 1 (to see if odd or even)
- #the last bit in an integer determines whether odd or even
- #used to read (integer - (integer >> 1 << 1)), which is more complicated
- if integer & 1 == 1:
- return number
- return 0
- #the returned function will be mapped across a list of one element
- #as it does so, the list will grow, so it will map across those elements too
- #it's a loop of sorts
- #the return values from the map function calls will be summed up
- return iterative_multiplication
- sum_map_key = bind_multiplication_to_list(data_list)
- product = sum(map(sum_map_key, data_list))
- return product
- #0 multiplied by anything is 0
- if 0 == x or 0 == y:
- return 0
- #calculate whether positive or negative
- x_positive = x == abs(x)
- y_positive = y == abs(y)
- if (x_positive and y_positive) or (not x_positive and not y_positive):
- #sign function will be used on final product to change its sign to the right sign
- def sign(n)->"probably a float or int":
- return +n
- else:
- def sign(n)->"probably a float or int":
- return -n
- #multiplying int by float is easier than float by float
- if isinstance(x, int):
- x, y = abs(x), abs(y)
- product = optimised_multiply(x, y)
- return sign(product)
- elif isinstance(y, int):
- x, y = abs(x), abs(y)
- product = optimised_multiply(y, x)
- return sign(product)
- else:
- def divide_by_power_of_2(a: int, b: int and 2 ** int() )->float:
- #uses fact that denominator is two powers of two multiplied together
- #therefore still in form 2**n
- b_log_2 = b.bit_length() - 1
- #change from 2**n to 2**-n
- #probably could just do b**-1, but this is more fun
- reciprocal_of_b = pow(2, -b_log_2)
- product = optimised_multiply(a, reciprocal_of_b)
- return sign(product)
- x, y = abs(x), abs(y)
- #both inputs are floats.
- #.as_integer_ratio() will return a tuple (numerator, denominator)
- #both are integers
- #denominator is a power of two because floats are represented internally as binary
- x_fraction = float(x).as_integer_ratio()
- y_fraction = float(y).as_integer_ratio()
- #multiply numerator and denominator separately
- #then multiply numerator by inverse of denominator
- numerator = optimised_multiply(x_fraction[0], y_fraction[0])
- denominator = optimised_multiply(x_fraction[1], y_fraction[1])
- return divide_by_power_of_2(numerator, denominator)
- def fallback_repeated_addition(integer, some_object)->"object":
- #sums a sequence of length equal to integer
- #each element of sequence is some_object
- #(also checks if integer is 0 or less, and tries to return an empty sequence)
- class AdditiveIdentity:
- #we can't use a copy of some_object and adjust the range because
- #sum explicitly disallows strings as start values
- #hence we create our own start value in place of [], "" or 0
- #as an added bonus, it means we can multiply anything by 1
- #I'd like to see the inbuilt functions do that!
- def __add__(self, other):
- return other
- def return_the_object(_)->"object":
- #intentionally ignores input
- return some_object
- if integer <= 0:
- #need an empty sequence of appropriate type
- try:
- return type(some_object)()
- except TypeError as original_exception:
- message = f"Can't create empty sequence of type {type(some_object)}!"
- raise TypeError(message) from original_exception
- sequence_of_integer_length = range(integer)
- #we need to set sum's start parameter because sum automatically starts with 0
- #0 doesn't work for concatenating lists or strings.
- sequence_of_objects = map(return_the_object, sequence_of_integer_length)
- return sum(sequence_of_objects, AdditiveIdentity())
- if isinstance(x, (float, int)) and isinstance(y, (float, int)):
- #multiply_real can handle integers or floats, or both
- return multiply_real(x, y)
- elif isinstance(x, complex) or isinstance(y, complex):
- #complex multiplication: (a+bj)(x+yj) = ax - by + (bx + ay)j
- x, y = complex(x), complex(y)
- real_part = multiply_real(x.real, y.real) - multiply_real(x.imag, y.imag)
- imag_part = multiply_real(x.imag, y.real) + multiply_real(x.real, y.imag)
- return complex(real_part, imag_part)
- elif isinstance(x, int):
- #I could also check if x has a .__int__ method
- #but that's beyond the scope of normal multiplication
- return fallback_repeated_addition(x, y)
- elif isinstance(y, int):
- return fallback_repeated_addition(y, x)
- else:
- message = f"What the heck am I supposed to do with {x}: {type(x)} and {y}: {type(y)}?"
- raise TypeError(message)
- if __name__ == "__main__":
- string1 = input("First value to multiply:")
- string2 = input("Second value to multiply:")
- #I can't be bothered building my own number parser
- val1, val2 = eval(string1), eval(string2)
- print(multiply(val1, val2))
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement