ActiveState Code

Recipe 393265: Substitute Decimals for floats in expressions


Evaluate using Decimals instead of floats.

Python
 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)

Discussion

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.

Sign in to comment