Advertisement
Guest User

Untitled

a guest
Feb 22nd, 2019
101
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 8.54 KB | None | 0 0
  1. #code by IFcoltransG
  2. #before we start, I'm not sorry for all the nested functions.
  3.  
  4. #no explicit loops, div, divmod, modulo, or multiplication
  5. def multiply(x, y)->"int, float or complex (or a sequence type)":
  6.     #accepts any combination of int, float, and complex
  7.     #can also multiply sequences, including strings, by an integer
  8.    
  9.     def multiply_real(x, y)->"float or int":
  10.         #multiply_real does some initial checks and ensures numbers end up with the correct sign
  11.         #simplifies multiplying two floats to multiplying ints, then multiplying an int by a float
  12.         #delegates to optimised_multiply when multiplying an int by a float
  13.         #for floats 0->1, error is up to about ±1E-17
  14.         #for floats 0->100, error can go up to ±1E-13
  15.        
  16.         def optimised_multiply(integer, number)->"float or int":
  17.             data_list = [(integer, number)]
  18.            
  19.             def bind_multiplication_to_list(data_list)->"function":
  20.                 #stores a reference to data_list in the closure of iterative_multiplication
  21.                 #not strictly necessary to store it in the closure, because it can be accessed from
  22.                 # within the function scope, but neater this way I think
  23.                 def iterative_multiplication(data)->"float or int":
  24.                     #"Russian Shepard algorithm" on an integer and another number
  25.                     #much faster than the old way I used—making a range(integer),
  26.                     #mapping a (lambda _: number) onto the range, then taking the sum()
  27.                     #based on a while loop that I based on a recursive function I made
  28.                    
  29.                     #extracts data from tuple
  30.                     #integer will be halved, number will be doubled
  31.                     #if integer was odd, number is added to the sum
  32.                     #(Russian Peasant algorithm leverages an integer's binary form)
  33.                     integer, number = data
  34.                     if integer <= 0:
  35.                         #integer should not be negative
  36.                         return 0
  37.                     else:
  38.                         #can't bitshift a float, so we double number by adding it to itself
  39.                         #can bitshift the integer though. bitshifting rounds down every time
  40.                         new_data = (integer >> 1, number + number)
  41.                         #modifies the object being mapped while iterating through it.
  42.                         #Do not do this in regular code!
  43.                         #I'm only doing it to avoid explicit loops and recursion
  44.                         data_list.append(new_data)
  45.                         #calculates integer % 2 and compares it to 1 (to see if odd or even)
  46.                         #the last bit in an integer determines whether odd or even
  47.                         #used to read (integer - (integer >> 1 << 1)), which is more complicated
  48.                         if integer & 1 == 1:
  49.                             return number
  50.                         return 0
  51.                
  52.                 #the returned function will be mapped across a list of one element
  53.                 #as it does so, the list will grow, so it will map across those elements too
  54.                 #it's a loop of sorts
  55.                 #the return values from the map function calls will be summed up
  56.                 return iterative_multiplication
  57.            
  58.             sum_map_key = bind_multiplication_to_list(data_list)
  59.             product = sum(map(sum_map_key, data_list))
  60.             return product
  61.        
  62.         #0 multiplied by anything is 0
  63.         if 0 == x or 0 == y:
  64.             return 0
  65.         #calculate whether positive or negative
  66.         x_positive = x == abs(x)
  67.         y_positive = y == abs(y)
  68.         if (x_positive and y_positive) or (not x_positive and not y_positive):
  69.             #sign function will be used on final product to change its sign to the right sign
  70.            
  71.             def sign(n)->"probably a float or int":
  72.                 return +n
  73.            
  74.         else:
  75.            
  76.             def sign(n)->"probably a float or int":
  77.                 return -n
  78.        
  79.         #multiplying int by float is easier than float by float
  80.         if isinstance(x, int):
  81.             x, y = abs(x), abs(y)
  82.             product = optimised_multiply(x, y)
  83.             return sign(product)
  84.         elif isinstance(y, int):
  85.             x, y = abs(x), abs(y)
  86.             product = optimised_multiply(y, x)
  87.             return sign(product)
  88.         else:
  89.            
  90.             def divide_by_power_of_2(a: int, b: int and 2 ** int() )->float:
  91.                 #uses fact that denominator is two powers of two multiplied together
  92.                 #therefore still in form 2**n
  93.                
  94.                 b_log_2 = b.bit_length() - 1
  95.                
  96.                 #change from 2**n to 2**-n
  97.                 #probably could just do b**-1, but this is more fun
  98.                 reciprocal_of_b = pow(2, -b_log_2)
  99.                 product = optimised_multiply(a, reciprocal_of_b)
  100.                 return sign(product)
  101.            
  102.             x, y = abs(x), abs(y)
  103.             #both inputs are floats.
  104.             #.as_integer_ratio() will return a tuple (numerator, denominator)
  105.             #both are integers
  106.             #denominator is a power of two because floats are represented internally as binary
  107.             x_fraction = float(x).as_integer_ratio()
  108.             y_fraction = float(y).as_integer_ratio()
  109.             #multiply numerator and denominator separately
  110.             #then multiply numerator by inverse of denominator
  111.             numerator = optimised_multiply(x_fraction[0], y_fraction[0])
  112.             denominator = optimised_multiply(x_fraction[1], y_fraction[1])
  113.             return divide_by_power_of_2(numerator, denominator)
  114.    
  115.     def fallback_repeated_addition(integer, some_object)->"object":
  116.         #sums a sequence of length equal to integer
  117.         #each element of sequence is some_object
  118.         #(also checks if integer is 0 or less, and tries to return an empty sequence)
  119.        
  120.         class AdditiveIdentity:
  121.             #we can't use a copy of some_object and adjust the range because
  122.             #sum explicitly disallows strings as start values
  123.             #hence we create our own start value in place of [], "" or 0
  124.             #as an added bonus, it means we can multiply anything by 1
  125.             #I'd like to see the inbuilt functions do that!
  126.            
  127.             def __add__(self, other):
  128.                 return other
  129.        
  130.         def return_the_object(_)->"object":
  131.             #intentionally ignores input
  132.             return some_object
  133.        
  134.         if integer <= 0:
  135.             #need an empty sequence of appropriate type
  136.             try:
  137.                 return type(some_object)()
  138.             except TypeError as original_exception:
  139.                 message = f"Can't create empty sequence of type {type(some_object)}!"
  140.                 raise TypeError(message) from original_exception
  141.         sequence_of_integer_length = range(integer)
  142.         #we need to set sum's start parameter because sum automatically starts with 0
  143.         #0 doesn't work for concatenating lists or strings.
  144.         sequence_of_objects = map(return_the_object, sequence_of_integer_length)
  145.         return sum(sequence_of_objects, AdditiveIdentity())
  146.    
  147.     if isinstance(x, (float, int)) and isinstance(y, (float, int)):
  148.         #multiply_real can handle integers or floats, or both
  149.         return multiply_real(x, y)
  150.     elif isinstance(x, complex) or isinstance(y, complex):
  151.         #complex multiplication: (a+bj)(x+yj) = ax - by + (bx + ay)j
  152.         x, y = complex(x), complex(y)
  153.         real_part = multiply_real(x.real, y.real) - multiply_real(x.imag, y.imag)
  154.         imag_part = multiply_real(x.imag, y.real) + multiply_real(x.real, y.imag)
  155.         return complex(real_part, imag_part)
  156.     elif isinstance(x, int):
  157.         #I could also check if x has a .__int__ method
  158.         #but that's beyond the scope of normal multiplication
  159.         return fallback_repeated_addition(x, y)
  160.     elif isinstance(y, int):
  161.         return fallback_repeated_addition(y, x)
  162.     else:
  163.         message = f"What the heck am I supposed to do with {x}: {type(x)} and {y}: {type(y)}?"
  164.         raise TypeError(message)
  165.  
  166. if __name__ == "__main__":
  167.     string1 =  input("First value to multiply:")
  168.     string2 =  input("Second value to multiply:")
  169.     #I can't be bothered building my own number parser
  170.     val1, val2 = eval(string1), eval(string2)
  171.     print(multiply(val1, val2))
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement