Evaluate using Decimals instead of floats.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | import re
number_pattern = re.compile(r"((\A|(?<=\W))(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?)")
def deciexpr(expr):
"""Substitute Decimals for floats in an expression string.
>>> from decimal import Decimal
>>> s = '+21.3e-5*85-.1234/81.6'
>>> deciexpr(s)
"+Decimal('21.3e-5')*Decimal('85')-Decimal('.1234')/Decimal('81.6')"
>>> eval(s)
0.016592745098039215
>>> eval(deciexpr(s))
Decimal("0.01659274509803921568627450980")
"""
return number_pattern.sub(r"Decimal('\1')", expr)
|
The numeric part of the regular expression is a direct translation from the decimal module's docs for Decimal object. See section 2 at http://docs.python.org/lib/module-decimal.html
For clarity, the regular expression does not scan for NaNs and Infinities. Those are trivial to incorporate if necessary.
Note, the optional leading sign is excluded from the pattern -- this is consistent with what Python does for other numeric literals (-0.1 lexes as a unary minus operation on 0.1). See http://docs.python.org/ref/numbers.html
The token separator part of the regular expression assures that numbers either start at the beginning of the string or are preceded by a single non-alphanumeric character. This prevents spurious mistranslations of valid Python identifiers such as vec1, vec2, etc.