I did some work this. The initial, posted yesterday, only took a couple of hours of work. Today was a full day.
Warning: this code has not been tested.
# Module: Numbers
# Date Started: February 2, 2018
# Author: George Keith Watson, a.k.a. Keith Michael Collins
# Copyrights: (c) 2017 George Keith Watson, a.k.a. Keith Michael Collins
# Application: Chemistry Laboratory, including Medical.
# Documentation:
# 2018-02-03
# One of the primary goals of this module is to be able to guarantee particular levels of accuracy of results
# rather than trusting the float class of Python running on any machine's cpu to do everything without error.
# 2018-02-04
# Plans:
# add comparison and math operator overloading to Scientific.
# complete significant digits qualification of values using Scientific as standard.
# implement comparison and math operator overloading for different type operands.
#
import math
class Scientific:
"A number expressed entirely in scientific notation accounting for precision, i.e. significant digits which is decimal places+1"
registry = {}
def __init__(self, name, coefficient, exponent=1, decimalPlaces=None):
"For standardization purposes, the coefficient is scaled to number whose absolute value is greater than 0 and less than 10.\nExponent is any integer."
self.name = None
self.coefficient = None
self.exponent = None
self.decimalPlaces = None
self.qualifiedVal = None
if name != None and isinstance( name, str ):
self.name = name
self.set( coefficient, exponent, decimalPlaces=None )
Scientific.registry[name] = self
else:
raise Exception("Scientific constructor - invalid name passed to constructor:\t" + str(name) )
def set(self, coefficient, exponent=1, decimalPlaces=None ):
if exponent == None:
exponent = 1
elif isinstance( exponent, int ):
self.exponent = exponent
else:
raise Exception("Scientific self.set() - invalid exponent, must be int:\t" + str(exponent))
if coefficient != None and (isinstance(coefficient, float) or isinstance(coefficient, int)):
if isinstance(coefficient,int):
self.coefficient = float(coefficient)
else:
self.coefficient = coefficient
while math.fabs(self.coefficient) >= 10:
self.exponent += 1
self.coefficient /= 10
print(str(self.coefficient))
while math.fabs(self.coefficient) < 1:
self.exponent -= 1
self.coefficient *= 10
if decimalPlaces != None:
if isinstance(decimalPlaces, int) and decimalPlaces > 0:
pass
else:
raise Exception("Scientific self.set() - invalid decimalPlaces argument, must be positive integer:\t" + str( decimalPlaces ))
else:
self.decimalPlaces = len(str(self.coefficient)) - 2
elif isinstance(coefficient, Real):
pass
elif isinstance(coefficient, Rational):
pass
else:
raise Exception("Scientific self.set() - invalid coefficient, must be float, int, Real, or Rational:\t" + str(coefficient))
def qualifiedValue(self):
"compute value qualified by sigDigits or decimalPlaces"
if self.decimalPlaces != None:
multiplier = math.pow(10, self.decimalPlaces)
self.qualifiedVal = math.floor( self.coefficient * multiplier ) / multiplier
else:
self.qualifiedVal = self.coefficient
return self.qualifiedVal
def __str__(self):
string = str(self.coefficient)
while len(string) < self.decimalPlaces + 2:
string += "0"
return string + " E" + str(self.exponent)
class Rational:
registry = {}
def __init__(self, name, numerator, denominator, sigDigits=None, notQualified=False):
"set() must be called to properly update the state of the object once it is constructed."
self.name = None
self.numerator = None
self.denominator = None
self.sigDigits = None
self.notQualified = None
self.floatVal = None
self.qualifiedVal = None
if name != None and isinstance(name, str):
self.name = name
self.set( numerator, denominator, sigDigits, notQualified )
Rational.registry[name] = self
else:
raise Exception("Rational constructor - invalid name passed to constructor:\t" + str(name) )
def set(self, numerator=None, denominator=None, sigDigits=None, notQualified=False):
if numerator != None and isinstance(numerator, int):
self.numerator = numerator
if denominator != None:
if isinstance(denominator, int) and denominator != 0:
self.denominator = denominator
else:
self.denominator = 1
if sigDigits != None and isinstance(sigDigits, int) and sigDigits > 0:
self.sigDigits = sigDigits
else:
self.sigDigits = None
if numerator != None and denominator != None:
self.floatValue()
self.notQualified = notQualified
if not self.notQualified:
self.qualifiedValue()
return self
def leastCommonMultiple(n, m):
candidate = max(n, m)
while True:
if (candidate % n == 0) and (candidate % m == 0):
break
candidate += 1
return candidate
def floatValue(self):
self.floatVal = self.numerator / self.denominator
return self.floatVal
def qualifiedValue(self):
"compute value qualified by sigDigits or decimalPlaces"
if self.numerator != None and self.denominator != None:
self.floatValue()
if self.sigDigits != None:
# Use Scientific class to standardize: significant digits = decimal places + 1
pass
else:
self.qualifiedVal = self.floatVal
return self.qualifiedVal
return None
def isInteger(self):
if self.denominator == 1:
return True
return self.floatVal - math.floor(self.floatVal) == 0
def isWhole(self):
if self.isInteger():
return self.floatVal >= 0
return False
def isNatural(self):
if self.isWhole():
return self.floatVal >= 1
return False
def __eq__(self, other):
if isinstance(other, Rational):
lcm = self.leastCommonMultiple( self.denominator, other.denominator )
selfNumerator = self.numerator * ( lcm / self.denominator )
otherNumerator = other.numerator * ( lcm / other.denominator )
return selfNumerator == otherNumerator
return None
def __ne__(self, other):
if isinstance(other, Rational):
return not self.__eq__(other)
return None
def __gt__(self, other):
if isinstance(other, Rational):
lcm = self.leastCommonMultiple( self.denominator, other.denominator )
selfNumerator = self.numerator * ( lcm / self.denominator )
otherNumerator = other.numerator * ( lcm / other.denominator )
return selfNumerator > otherNumerator
return None
def __lt__(self, other):
if isinstance(other, Rational):
lcm = self.leastCommonMultiple( self.denominator, other.denominator )
selfNumerator = self.numerator * ( lcm / self.denominator )
otherNumerator = other.numerator * ( lcm / other.denominator )
return selfNumerator < otherNumerator
return None
def __ge__(self, other):
if isinstance(other, Rational):
return self.__gt__(other) or self.__eq__(other)
return None
def __le__(self, other):
if isinstance(other, Rational):
return self.__lt__(other) or self.__eq__(other)
return None
def __add__(self, other):
"result is returned as a new Rational instance"
if isinstance(other, Rational):
lcm = self.leastCommonMultiple( self.denominator, other.denominator )
selfNumerator = self.numerator * ( lcm / self.denominator )
otherNumerator = other.numerator * ( lcm / other.denominator )
return Rational("__add__result", selfNumerator + otherNumerator, lcm, self.sigDigits, self.notQualified)
return None
def __sub__(self, other):
"result is returned as a new Rational instance"
if isinstance(other, Rational):
lcm = self.leastCommonMultiple( self.denominator, other.denominator )
selfNumerator = self.numerator * ( lcm / self.denominator )
otherNumerator = other.numerator * ( lcm / other.denominator )
return Rational("__sub__result", selfNumerator - otherNumerator, lcm, self.sigDigits, self.notQualified)
return None
def __mul__(self, other):
"result is returned as a new Rational instance"
if isinstance(other, Rational):
return Rational("__mul__result", self.numerator * other.numerator, self.denominator * other.denominator, self.sigDigits, self.notQualified)
return None
def __truediv__(self, other):
"result is returned as a new Rational instance"
if isinstance(other, Rational):
# check for divide by zero
if other.numerator == 0:
raise Exception("Rational number division - DIVIDE BY ZERO ATTEMPTED")
lcm = self.leastCommonMultiple( self.denominator, other.denominator )
selfNumerator = self.numerator * ( lcm / self.denominator )
otherNumerator = other.numerator * ( lcm / other.denominator )
return Rational("__truediv__result", round(selfNumerator / otherNumerator), lcm, self.sigDigits, self.notQualified)
return None
def __abs__(self):
"result is returned as a new Rational instance without a name"
if self.numerator > 0:
if self.denominator > 0:
return Rational("__abs__result", self.numerator, self.denominator)
else:
return Rational("__abs__result", self.numerator, -self.denominator)
elif self.denominator > 0:
return Rational("__abs__result", -self.numerator, self.denominator)
return Rational("__abs__result", -self.numerator, -self.denominator)
def __str__(self):
return str(self.numerator) + " / " + (str(self.denominator))
class Real:
registry = {}
# sigDigits is always used if present and overrides decimalPlaces as the precision guarantee.
#
def __init__(self, name, value, sigDigits=None, notQualified=None):
"set() must be called to properly update the state of the object once it is constructed."
self.name = None
self.value = None
self.sigDigits = None
self.notQualified = None
# where possible, store value qualified by sigDigits or decimalPlaces for convenience and efficiency
self.qualifiedVal = None
if name != None and isinstance(name, str) and value != None and (isinstance(value, float) or isinstance(value, int)):
self.name = name
self.set(value, sigDigits, notQualified)
Real.registry[name] = self
# qualified value is always used unless the user specifies that the raw value can be used.
# qualified means sigDigits or decimalPlaces is used.
#
def set(self, value=None, sigDigits=None, notQualified=False):
if value != None and (isinstance(value,float) or isinstance(value,int)):
self.value = value
self.qualifiedVal = value
if sigDigits != None and isinstance(sigDigits,int) and sigDigits > 0:
self.sigDigits = sigDigits
else:
self.sigDigits = None
self.notQualified = notQualified
if not self.notQualified:
self.qualifiedValue()
return self
def qualifiedValue(self):
"compute value qualified by sigDigits or decimalPlaces"
if self.sigDigits != None:
# Use Scientific class to standardize: significant digits = decimal places + 1
pass
else:
self.qualifiedVal = self.value
return self.qualifiedVal
# for operator overloading any type other than Real must be imported into this class, so a temporary Real is
# constructed.
def _import(self, other, name=None):
if isinstance(other,Real):
return other
if name == None:
name = "no name"
if isinstance(other,int) or isinstance(other,float):
return Real( name, other, self.sigDigits, self.notQualified )
def __eq__(self, other):
other = self._import(other)
if self.notQualified:
return self.floatVal == other.floatVal
else:
return self.qualifiedVal == other.qualifiedValue()
def __ne__(self, other):
other = self._import(other)
return not self.__eq__(other)
def __ge__(self, other):
other = self._import(other)
if self.__eq__(other):
return True
return self.__gt__(other)
def __gt__(self, other):
other = self._import(other)
if self.notQualified:
return self.value > other.value
else:
return self.qualifiedVal > other.qualifiedValue()
def __le__(self, other):
other = self._import(other)
if self.__eq__(other):
return True
return self.__lt__(other)
def __lt__(self, other):
other = self._import(other)
if self.notQualified:
return self.value < other.value
else:
return self.qualifiedVal < other.qualifiedValue()
def __add__(self, other):
"result is returned as a new Real instance with name __add__result"
other = self._import(other)
if self.notQualified:
return Real( "__add__result", self.value + other.value, self.sigDigits, self.notQualified )
else:
return Real( "__add__result", self.qualifiedVal + other.qualifiedValue(), self.sigDigits, self.notQualified )
def __sub__(self, other):
"result is returned as a new Real instance with name __sub__result"
other = self._import(other)
if self.notQualified:
return Real( "__sub__result", self.value - other.value, self.sigDigits, self.notQualified )
else:
return Real( "__sub__result", self.qualifiedVal - other.qualifiedValue(), self.sigDigits, self.notQualified )
def __mul__(self, other):
"result is returned as a new Real instance with name __mul__result"
other = self._import(other)
if self.notQualified:
return Real( "__mul__result", self.value * other.value, self.sigDigits, self.notQualified )
else:
return Real( "__mul__result", self.qualifiedVal * other.qualifiedValue(), self.sigDigits, self.notQualified )
def __truediv__(self, other):
"result is returned as a new Real instance with name __div__result"
# check for divide by zero
other = self._import(other)
if self.notQualified:
return Real( "__truediv__result", self.value / other.value, self.sigDigits, self.notQualified )
else:
return Real( "__truediv__result", self.qualifiedVal / other.qualifiedValue(), self.sigDigits, self.notQualified )
def __abs__(self):
"result is returned as a new Real instance with name __abs__result"
if self.notQualified:
if self.value < 0:
absValue = -self.value
else:
absValue = self.value
return Real( "__abs__result", absValue, self.sigDigits, self.notQualified )
else:
if self.qualifiedVal < 0:
absValue = -self.qualifiedVal
else:
absValue = self.qualifiedVal
return Real( "__abs__result", absValue, self.sigDigits, self.notQualified )
def __str__(self):
if self.notQualified:
return str(self.value)
else:
return str(self.qualifiedVal)
class Imaginary:
registry = {}
def __init__(self, name, coefficient):
self.name = None
self.coefficient = None
if name != None and isinstance(name, str) and coefficient != None and \
(isinstance(coefficient,int) or isinstance(coefficient, float) or isinstance(coefficient,Real)):
self.name = name
self.coefficient = coefficient
Imaginary.registry[name] = self
def set(self, coefficient):
if coefficient != None and (isinstance(coefficient,int) or isinstance(coefficient, float) or isinstance(coefficient,Real)):
self.coefficient = coefficient
return self
def __str__(self):
return str(self.coefficient) + "i"
class Complex:
registry = {}
def __init__(self, name, x, y):
"z = x + iy"
self.x = None
self.y = None
self.name = None
if name != None and isinstance(name, str) and x != None and (isinstance(x, float) or isinstance(x,Real)) and \
y != None and (isinstance(y,float) or isinstance(y,Real)):
self.name = name
self.x = x
self.y = y
Complex.registry[name] = self
def set(self, x=None,y=None):
if x != None and (isinstance(x, float) or isinstance(x,Real)):
self.x = x
if y != None and (isinstance(y, float) or isinstance(y,Real)):
self.y = y
return self
def __str__(self):
return str(self.x) + " + " + str(self.y) + "i"