#!/usr/bin/env python # -*- coding: UTF-8 -*- '''POC for EXACT precision arithmetic that provides arbitrary precision OUTPUT. A bit of Guido's time machine here: the decimal class provides Decimals that act like people expect from school. This makes this implementation partly piggy-backed on the Decimal class, but Numbers are stored with absolute precision and given decimal place approximation are published on demand. All of the arbitrary precision libraries I've seen, including Python's, allow you to specify a given arbitrary but finite precision, and then work with that precision. This method allows you to work with absolute precision for any computable number you have the algorithm for (i.e. you can make a generator that will yield any number of digits in a finite amount of time), and then on-demand print a decimal approximation. Later on change your mind and want an extra digit? No problem, just ask for one more digit. Same goes for twice as many digits. At present implementation of integers and decimals is obvious. But the solution is generic to any computable number: override get_places with something that will get the requested number of decimal places, and you have an exact way to represent π or e as precisely as any algebraic number. You need to furnish the algorithm, but you are able to furnish the algorithm and work with any computable number you have the algorithm for. This software is available to you under your choice of the GPLv2 or MIT licenses.''' import decimal ADDITION = 0 SUBTRACTION = 1 MULTIPLICATION = 2 DIVISION = 3 EXPONENTIATION = 4 BUFFER_DIGITS = 2 class Number(object): '''A representation of a number, possibly broken down into other numbers. The integer value, if non-None, is a sign that the Number is simply that integer. If it is non-None, there are a first and second operands, each of which should be a Number. operation defines which arithmetic operation; at present the primitives do not directly parse negative numbers but -n can be represented as arithmetic.Number(0) - arithmetic.Number(n). As built in, the structure is recursive boiling to integers joined by algebraic operations. The class can be subclassed, and get_places() overridden, to create any computable number, i.e. any number for which a generator can be written that will yield any finite number of digits in some finite amount of time.''' integer = None first = None second = None operation = None def __init__(self, initial_value = None, first = None, second = None, operation = None): '''Takes as its first non-self argument numeric types or a string. In general, the expected user initialization will be with an initial value, and initializations provided by the class, which acts as its own factory, will leave the initial value as None and populate the first and second Number arguments and the operation between them. If the first argument is None, it populates a Number composed of two other Numbers and the operand joining them.''' if initial_value != None: as_string = str(initial_value) if as_string[0] == '-': self.first = Number(0) self.second = Number(as_string[1:]) self.operation = SUBTRACTION elif not '.' in as_string: self.integer = initial_value else: integer, fraction = as_string.split('.') if integer == '': integer = 0 self.first = Number((int(integer) * (10 ** len(fraction)) + int(fraction))) self.second = Number(10 ** len(fraction)) self.operation = DIVISION else: self.first = first self.second = second self.operation = operation def __add__(self, other): '''Factory method to produce the sum of two Numbers.''' return Number(None, self, other, ADDITION) def __div__(self, other): '''Factory method to produce the quotient of two Numbers.''' return Number(None, self, other, DIVISION) def __mul__(self, other): '''Factory method to produce the product of two Numbers.''' return Number(None, self, other, MULTIPLICATION) def __pow__(self, other): '''Factory method to produce the exponentiation of two Numbers.''' return Number(None, self, other, EXPONENTIATION) def get_places(self, places): '''Creates string wrapping a print-on-demand n decimal place number.''' if self.integer != None: return str(self.integer) + '.' + '0' * places else: if self.operation == ADDITION: raw = (decimal.Decimal(self.first.get_places(places + BUFFER_DIGITS)) + decimal.Decimal(self.second.get_places(places + BUFFER_DIGITS))) elif self.operation == SUBTRACTION: raw = (decimal.Decimal(self.first.get_places(places + BUFFER_DIGITS)) - decimal.Decimal(self.second.get_places(places + BUFFER_DIGITS))) elif self.operation == MULTIPLICATION: raw = (decimal.Decimal(self.first.get_places(places + BUFFER_DIGITS)) * decimal.Decimal(self.second.get_places(places + BUFFER_DIGITS))) elif self.operation == DIVISION: raw = (decimal.Decimal(self.first.get_places(places + BUFFER_DIGITS)) / decimal.Decimal(self.second.get_places(places + BUFFER_DIGITS))) elif self.operation == EXPONENTIATION: raw = (decimal.Decimal(self.first.get_places(places + BUFFER_DIGITS)) ** decimal.Decimal(self.second.get_places(places + BUFFER_DIGITS))) decimal.getcontext().prec = places + BUFFER_DIGITS rounded = raw.quantize(decimal.Decimal(str(.1 ** places)), rounding=decimal.ROUND_HALF_UP) return str(rounded)