Last active
September 14, 2020 14:59
-
-
Save msoxzw/d996348937efc6ade5ab054fa2deba76 to your computer and use it in GitHub Desktop.
Infinite-precision calculator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Requirement:
Python >= 3.6
python Calculator.py -f
python Calculator.py -d -p 30
python Calculator.py