如何在使用eval时将float转换为Decimal?

时间:2019-06-08 14:14:41

标签: python

我是编程的新手,所以我想在这里寻求帮助。 所以当我使用:

eval('12.5 + 3.2'),

它将12.5和3.2转换为浮点数。 但是我希望将它们转换为Decimal数据类型。

我可以使用:

from decimal import Decimal
eval(Decimal(12.5) + Decimal(3.2))

但是我在接受用户输入时无法在程序中做到这一点。

我找到了一个解决方案,但是它使用了正则表达式,我现在不熟悉它(由于某种原因,我无法再次找到它。)

如果有人可以帮助我,那将是很棒的。谢谢!

1 个答案:

答案 0 :(得分:7)

更新:显然,官方文档的配方完全可以满足您的需求。来自https://docs.python.org/3/library/tokenize.html#examples

from tokenize import tokenize, untokenize, NUMBER, STRING, NAME, OP
from io import BytesIO

def decistmt(s):
    """Substitute Decimals for floats in a string of statements.

    >>> from decimal import Decimal
    >>> s = 'print(+21.3e-5*-.1234/81.7)'
    >>> decistmt(s)
    "print (+Decimal ('21.3e-5')*-Decimal ('.1234')/Decimal ('81.7'))"

    The format of the exponent is inherited from the platform C library.
    Known cases are "e-007" (Windows) and "e-07" (not Windows).  Since
    we're only showing 12 digits, and the 13th isn't close to 5, the
    rest of the output should be platform-independent.

    >>> exec(s)  #doctest: +ELLIPSIS
    -3.21716034272e-0...7

    Output from calculations with Decimal should be identical across all
    platforms.

    >>> exec(decistmt(s))
    -3.217160342717258261933904529E-7
    """
    result = []
    g = tokenize(BytesIO(s.encode('utf-8')).readline)  # tokenize the string
    for toknum, tokval, _, _, _ in g:
        if toknum == NUMBER and '.' in tokval:  # replace NUMBER tokens
            result.extend([
                (NAME, 'Decimal'),
                (OP, '('),
                (STRING, repr(tokval)),
                (OP, ')')
            ])
        else:
            result.append((toknum, tokval))
    return untokenize(result).decode('utf-8')

然后您可以像这样使用:

from decimal import Decimal
s = "12.5 + 3.2 + 1.0000000000000001 + (1.0 if 2.0 else 3.0)"
s = decistmt(s)
print(s)
print(eval(s))

结果:

Decimal ('12.5')+Decimal ('3.2')+Decimal ('1.0000000000000001')+(Decimal ('1.0')if Decimal ('2.0')else Decimal ('3.0'))
17.7000000000000001

随意跳过此答案的其余部分,现在只有半正确解的历史学家才感兴趣。


据我所知,没有简单的方法可以“挂钩” eval来更改其解释浮动对象的方式。

但是,如果我们在ast之前使用eval模块将您的字符串转换为抽象语法树,那么我们可以操纵该树,以Decimal调用替换浮点数。

import ast
from decimal import Decimal

def construct_decimal_node(value):
    return ast.Call(
        func = ast.Name(id="Decimal", ctx=ast.Load()),
        args = [value],
        keywords = []
    )
    return expr

class FloatLiteralReplacer(ast.NodeTransformer):
    def visit_Num(self, node):
        return construct_decimal_node(node)

s = '12.5 + 3.2'
node = ast.parse(s, mode="eval")
node = FloatLiteralReplacer().visit(node)
ast.fix_missing_locations(node) #add diagnostic information to the nodes we created
code = compile(node, filename="", mode="eval")
result = eval(code)
print("The type of the result of this expression is:", type(result))
print("The result of this expression is:", result)

结果:

The type of the result of this expression is: <class 'decimal.Decimal'>
The result of this expression is: 15.70000000000000017763568394

如您所见,结果与直接计算Decimal(12.5) + Decimal(3.2)所得到的结果相同。


但是也许您在想“为什么结果15.7不?”。这是因为Decimal(3.2)与3.2不完全相同。它实际上等于3.20000000000000017763568394002504646778106689453125。当使用浮点对象初始化小数时,这是一种危险-不准确已经存在。最好使用字符串来创建小数,例如Decimal("3.2")

也许您现在正在思考“好吧,那我该如何将12.5 + 3.2变成Decimal("12.5") + Decimal("3.2")?”。最快的方法是修改construct_decimal_node,以便Call的args是ast.Str而不是ast.Num:

import ast
from decimal import Decimal

def construct_decimal_node(value):
    return ast.Call(
        func = ast.Name(id="Decimal", ctx=ast.Load()),
        args = [ast.Str(str(value.n))],
        keywords = []
    )
    return expr

class FloatLiteralReplacer(ast.NodeTransformer):
    def visit_Num(self, node):
        return construct_decimal_node(node)

s = '12.5 + 3.2'
node = ast.parse(s, mode="eval")
node = FloatLiteralReplacer().visit(node)
ast.fix_missing_locations(node) #add diagnostic information to the nodes we created
code = compile(node, filename="", mode="eval")
result = eval(code)
print("The type of the result of this expression is:", type(result))
print("The result of this expression is:", result)

结果:

The type of the result of this expression is: <class 'decimal.Decimal'>
The result of this expression is: 15.7

但是要当心:虽然我希望这种方法在大多数情况下都能返回良好的结果,但在极端情况下它会返回令人惊讶的结果。特别是当表达式包含浮点数f这样的float(str(f)) != f时。换句话说,当浮标的打印表示形式缺乏精确表示浮标所需的精度时。

例如,如果将以上代码中的s更改为"1.0000000000000001 + 0",则结果将为1.0。这是不正确的,因为Decimal("1.0000000000000001") + Decimal("0")的结果是1.0000000000000001

我不确定如何解决此问题... ast.parse执行完毕时,float文字已被转换为float对象,并且没有明显的方法来检索字符串那是用来创建它的。也许您可以从表达式字符串中提取它,但是从根本上讲,您必须重新发明Python的解析器才能做到这一点。