Skip to content

Instantly share code, notes, and snippets.

@msoxzw
Last active September 14, 2020 14:59
Show Gist options
  • Save msoxzw/d996348937efc6ade5ab054fa2deba76 to your computer and use it in GitHub Desktop.
Save msoxzw/d996348937efc6ade5ab054fa2deba76 to your computer and use it in GitHub Desktop.
Infinite-precision calculator
import code
from argparse import ArgumentParser
from decimal import getcontext
from functools import update_wrapper
from sys import float_info
from tokenize import generate_tokens, untokenize, NUMBER, STRING, NAME, OP
operations = [
f'__{a}{b}__' for a in ['', 'r', 'i']
for b in ['add', 'sub', 'mul', 'matmul', 'truediv',
'floordiv', 'mod', 'divmod', 'pow', 'lshift',
'rshift', 'and', 'xor', 'or']
] + ['__neg__', '__pos__', '__abs__', '__invert__', '__round__']
def convert_operation(operation):
def wrapper(self, *args, **kwargs):
return type(self)(operation(self, *args, **kwargs))
update_wrapper(wrapper, operation)
return wrapper
def convert_operations(cls):
for operation in operations:
method = getattr(cls, operation, None)
if callable(method):
setattr(cls, operation, convert_operation(method))
return cls
def init_number(base):
methods = {
'__repr__': base.__str__,
}
try:
for name, method in methods.items():
setattr(base, name, method)
except TypeError:
return convert_operations(type(base.__name__, (base,), methods))
return base
class LineBuffer:
def __init__(self, buffer):
self.line = iter(buffer)
def __call__(self):
return next(self.line) + '\n'
class InteractiveConsole(code.InteractiveConsole):
def runcode(self, codes):
tokens = []
for token in generate_tokens(LineBuffer(self.buffer)):
if token.type == NUMBER:
tokens.extend([
(NAME, 'Number'),
(OP, '('),
(STRING, repr(token.string)),
(OP, ')')
])
else:
tokens.append((token.type, token.string))
source = untokenize(tokens)
super().runcode(code.compile_command(source))
def main():
parser = ArgumentParser(description='Infinite-precision calculator')
group = parser.add_mutually_exclusive_group()
group.add_argument('-d', '--decimal', action='store_true',
help='decimal calculator with arbitrary precision')
group.add_argument('-f', '--float', action='store_true',
help='hardware floating point calculator with precision'
f' of {float_info.dig} decimal digits')
parser.add_argument('-p', '--precision', metavar='P', type=int,
default=getcontext().prec,
help='set the precision'
' for decimal calculator (default: %(default)s)')
args = parser.parse_args()
if args.float:
code.interact(
banner='Hardware floating point calculator with precision'
f' of {float_info.dig} decimal digits',
exitmsg=''
)
return
try:
import readline
except ImportError:
pass
if args.decimal:
getcontext().prec = args.precision
from decimal import Decimal as Base
banner = f'Decimal calculator with precision of {args.precision}' \
' decimal digits'
else:
from fractions import Fraction as Base
banner = 'Fraction calculator with infinite precision'
console = InteractiveConsole(locals={'Number': init_number(Base)})
console.interact(banner=banner, exitmsg='')
if __name__ == '__main__':
main()
@msoxzw
Copy link
Author

msoxzw commented Aug 28, 2020

Requirement:
Python >= 3.6

python Calculator.py -f

Hardware floating point calculator with precision of 15 decimal digits
>>> 0.1+0.2
0.30000000000000004
>>> 1/3*3
1.0

python Calculator.py -d -p 30

Decimal calculator with precision of 30 decimal digits
>>> 0.1+0.2
0.3
>>> 1/3*3
0.999999999999999999999999999999

python Calculator.py

Fraction calculator with infinite precision
>>> 0.1+0.2
3/10
>>> 1/3*3
1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment