#!/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)