将分数Fraction转换为小数Decimal的最佳方法是什么?

17

在Python中,标准库类fractions.Fractiondecimal.Decimal存在于帮助保持有理数计算精确的过程中。对于不熟悉的人来说,下面是一个例子,可以更好地说明它的作用:

>>> 1 / 10 * 3
0.30000000000000004
>>> decimal.Decimal('1') / 10 * 3
Decimal('0.3')
>>> fractions.Fraction('1') / 10 * 3
Fraction(3, 10)

我的问题是,如果我有一个Fraction,最好的方法是将其转换为Decimal

不幸的是,显而易见的解决方案行不通:

>>> decimal.Decimal(fractions.Fraction(3, 10))
Traceback (most recent call last):
  ...
TypeError: conversion from Fraction to Decimal is not supported

现在我正在使用这段代码:

>>> decimal.Decimal(float(fractions.Fraction(3, 10)))
Decimal('0.299999999999999988897769753748434595763683319091796875')

现在,当我实际输出这个值时,任何程度的四舍五入都会将其转换为0.3,而我只会在输出之前立即进行此转换(所有核心数学计算都采用 Fraction)。尽管如此,我认为无法从 Fraction(3, 10) 获得 Decimal('0.3') 有点傻。感谢任何帮助!


1
在Python中,fractions.Fractiondecimal.Decimal标准库类存在的目的是帮助保持有理数算术的精确性。但是需要注意的是,Decimal.decimal仍然是浮点数,只不过是基于10进制的浮点数。因此,Decimal(1) / Decimal(3)无法给出精确的答案。 - Steven Rumbalski
2个回答

22

不妨让 Decimal() 自己来处理分数的除法?

def decimal_from_fraction(frac):
    return frac.numerator / decimal.Decimal(frac.denominator)
这是 Fraction.__float__() 做的事情(简单地将分子除以分母),但通过将两个值中至少一个转换为 Decimal 对象,您可以控制输出结果。
这使您能够使用 decimal 上下文:
>>> decimal_from_fraction(fractions.Fraction(3, 10))
Decimal('0.3')
>>> decimal_from_fraction(fractions.Fraction(1, 55))
Decimal('0.01818181818181818181818181818')
>>> with decimal.localcontext() as ctx:
...    ctx.prec = 4
...    decimal_from_fraction(fractions.Fraction(1, 55))
...
Decimal('0.01818')

3
from_decimal是一个类方法,支持反向转换的原因是什么?frac = fractions.Fraction.from_decimal(decimal.Decimal('0.3')) 是有效的,但是 Decimal 没有 from_fraction 方法。 - roganjosh
@roganjosh:没有标准的钩子可以将值转换为另一个方向,而对于float()(以__float__的形式)则有。您可以向项目提交功能请求。 - Martijn Pieters
4
@roganjosh:啊,有一个功能请求,但该请求从未达成如何最佳实现的共识。 - Martijn Pieters
@MartijnPieters 啊,好吧,就在我正要问你是否认为这样的请求值得考虑时,你发了这个帖子,因为你已经提供了一个干净的解决方法。我主要是想知道为什么不能在另一个方向上干净地进行转换,是否我错过了某些基本的东西。谢谢你澄清了这一点 :) - roganjosh

1
这并不是一个全新的答案,因为Martijn的解决方案仍然是最佳选择,而是关于“适当”转换方法可能即将出现的一点说明。
Python 3.12引入了用于Fraction的浮点格式化, 允许使用以下相当巧妙的函数:
def decimal_from_fraction(frac):
    return decimal.Decimal(f"{frac:.<prec>f}")     # can also use `.g`

在这里,<prec> 是所需的精度。不幸的是,它必须在函数中固定,并且要有用,它必须超过全局Decimal上下文的精度。尽管如此,精度可以任意大。

所有这些都是为了说明直接转换为Decimal的功能已经具备了(包括陷阱和舍入),并且一些有争议的实现细节已经决定(坦率地说,没有太多的轰动)。希望这个故事很快能有个圆满的结局。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接