def init_from_value(cls, value: int, base=10, wrap_point=None): """Initialises a new DigitValue with the value of the integer specified. Accepts numeric-valid strings.""" polarity_bit = 0 if int(value) >= 0 else 1 wrap_point = base if wrap_point is None else wrap_point log_method('Initialise new custom number', value, f'in base {base}', wrap_point, priority_level=0) digits = str(abs(int(value))) return cls(digits, polarity_bit).convert_base(base, wrap_point)
def one_complement(self): """Return the one's complement of the number, as an array of byte-aligned binary. If the number is negative, this will flip all bits; ensures the first bit is 1.""" log_method("One's complement of", self, priority_level=1) padded_binary = self.pad_to_bytes() if self.polarity: return DigitList(flip_values(padded_binary, 1), BINARY) else: log_method("Value is positive: two's complement is equivalent to binary") return DigitList(padded_binary, BINARY)
def pad_to_bytes(self): """Pads digits (converted to binary) with zeroes such that bits are byte-aligned. Always returns an array of bits. Always pads at least one redundant zero, even if digits are already byte-aligned: (+/-)11111111 -> 0000000011111111.""" log_method('Pad to byte-aligned binary', self, priority_level=0) positive_equivalent = DigitValue(self.digits, 0, self.base, self.wrap_point) binary = positive_equivalent.convert_base(BINARY) bits_needed = len(binary.digits) # Returns the binary digit array, consisting of [padding...] + [digits...] padding_length = 8 - (bits_needed % 8) bits = pad_left(binary.digits, padding_length) log_method('Padded binary', bits, f'with padding length {padding_length}') return bits
def two_complement(self): """Return the two's complement of the number.""" log_method("Two's complement of", self, priority_level=1) if self.polarity: _one_complement = self.one_complement() log_method("Increment one's complement value to find two's complement", _one_complement, priority_level=0) # Pad "1" with leading zeroes, so it can be added to the one's complement. one = pad_left((1,), len(_one_complement.digits) - 1) incremented, _ = _one_complement.add_iterable(_one_complement.digits, one, _one_complement.base) return DigitList(incremented, BINARY) else: log_method("Value is positive: two's complement is equivalent to binary") return DigitList(self.pad_to_bytes(), BINARY)
def add_iterable(first_digits, second_digits, base): """Adds digits together within the base specified.""" if len(first_digits) != len(second_digits): raise UserWarning("the digits to be added have not been padded to \ the same length. This may cause digits to be skipped during addition.") log_method('Pair digits together and add', first_digits, second_digits, f'in base {base}', priority_level=1) result_digits = [] # Iterate through pairs of digits, least digit first, and perform addition. carry = 0 for first, second in zip(reversed(first_digits), reversed(second_digits)): _sum = first + second + carry log_method('Find sum of', first, second, carry, f'total {_sum}') if _sum >= base: carry, _sum = divmod(_sum, base) log_method(f'Carry {carry}') else: carry = 0 # Reset carry so that values are only carried over once. result_digits.insert(0, _sum) log_method('Partial addition result', result_digits, f'with carry {carry}') return result_digits, carry
def convert_base(self, base: int, wrap_point=None): """Implements repeated division to convert to and return a DigitValue of the specified base.""" wrap_point = base if wrap_point is None else wrap_point log_method('Base conversion', self, f'new base {base}', wrap_point, priority_level=1) if base == self.base and wrap_point == self.wrap_point: # Optimisation check. return DigitValue(self.digits, self.polarity, base, wrap_point) digits = [] value = abs(self.value()) run_at_least_once = False log_method('Use repeated division to find each place-value digit in the new base') while value > 0 or not run_at_least_once: value, new_digit = divmod(value, base) digits.insert(0, new_digit) # Remainders are inserted in reverse order. if USE_WORKING: log_method('Base divides value', f'result {value}', f'remainder {new_digit}') run_at_least_once = True log_method('Base conversion result', digits) return DigitValue(digits, self.polarity, base, wrap_point)
def evaluate_steps(steps): """Runs through a list of steps sanitised in parse_request() and attempts to execute the request's instructions & return working.""" working.clear() working.working = [] working.id_counter = count(0) if step_type(steps[0]) != NEW_NUMBER: raise ValueError('The list of steps must start with a number.') memory = None operation = None representations = [] for current_step in steps: working.current_step = [] _step_type = step_type(current_step) if _step_type == NEW_NUMBER: # The next step is a number, which is either placed in memory, # or if the last step was addition, added to the number in memory. if operation is None: working.log_method('Set stored number to', current_step, priority_level=2) memory = current_step elif operation == functions[0]: working.log_method('Addition', memory, current_step, priority_level=2) memory = memory + current_step operation = None else: raise ValueError( f'Unknown operation during steps execution {operation}.') elif _step_type == CONVERSION: conversion_types = current_step[1] working.log_method('Conversion', conversion_types, priority_level=2) memory = memory.convert_base(conversion_types[0], conversion_types[1]) elif _step_type == FUNCTION: f_name = current_step[1] if f_name == functions[0]: working.log_method('Add the next number', priority_level=2) operation = f_name else: # Get the requested method's value. working.log_method('Display', f_name, priority_level=2) x = digit_method_value(memory, f_name) representations.append(x) working.log_method('Result', x, priority_level=1) # Record any working, if any has been added to the log during this step. if working.USE_WORKING and working.current_step: working.working.append(working.current_step) if step_type(memory) == NEW_NUMBER: answer = str(memory) else: raise ValueError('Corrupted memory during steps execution.') return answer, working.working
def __add__(self, other): """Add together two DigitValue instances. Positive numbers are added within the base of the left component. Negative numbers add using binary two's complement. Returns its answer in the base of the left component.""" if not isinstance(other, self.__class__): raise TypeError('unsupported operand type(s) for +. Addition \ is only supported between two DigitValue() instances.') _self = self is_two_complement = [False, False] # Converts negative numbers to two's complement for cleaner addition. if self.polarity: log_method("First number is negative. Convert to two's complement", priority_level=0) _self = self.two_complement() is_two_complement[0] = True if other.polarity: log_method("Second number is negative. Convert to two's complement", priority_level=0) other = other.two_complement() is_two_complement[1] = True # After the first's base is set, convert the other component to the same base. log_method('Convert both numbers to the same base') if any(is_two_complement): if not is_two_complement[0]: # Converts if not already a binary complement. _self = _self.convert_base(other.base, other.wrap_point) elif not is_two_complement[1]: other = other.convert_base(_self.base, _self.wrap_point) else: other = other.convert_base(self.base, self.wrap_point) first_digits = _self.digits second_digits = other.digits # Pad each to the same length, so that the place values match up for each digit. length_needed = max(len(first_digits), len(second_digits)) first_digits = pad_left(first_digits, length_needed - len(first_digits), fillchar=int(is_two_complement[0])) second_digits = pad_left(second_digits, length_needed - len(second_digits), fillchar=int(is_two_complement[1])) # Add the digits together; store the result as two's complement in result_digits. result_digits, carry = self.add_iterable(first_digits, second_digits, _self.base) if not any(is_two_complement): # For positive values the carry must also be checked, # since it holds the sum of the most significant bits. if carry: result_digits.insert(0, carry) # For positive two's complement, the first digit must stay a zero. if result_digits[0] != 0: result_digits.insert(0, 0) log_method('As neither number is negative, the carry is checked and added to the result.', result_digits) else: # If either component is negative the carry can be safely ignored. log_method('As one of the numbers is negative, the carry can be safely ignored') # Convert the final sum to its non-normalised value. result_value = two_complement_value(result_digits, _self.base) # Finally, switch back to the original base of the first component. result = DigitValue.init_from_value(result_value, self.base, self.wrap_point) log_method(f'Addition result: {result}', priority_level=1) return result