Python - 浮点数商的安全转换为整数

3
在我的情况下,我试图将一个浮点数p除以另一个q。分子是分母的倍数,并且两者具有以下属性:
  1. 可以用十进制表示
  2. 最多有3或4个有效数字
  3. 介于11e-8之间。
(例如,p = .0014q = .00002
在完美的世界里,除法结果应该是一个完美的整数(这里是70)。但是浮点运算经常不完美
我想要最简单、最安全、最高效的方法来避免将商强制转换为int时返回p/q - 1的错误。
我现在的最佳解决方案是像这样做:
int(p/q + 1e-10)

但那种方法感觉不太干净,可能不如其他可能的方法有效。

另外,我知道可以四舍五入,但这在代码中似乎会误导,并且可能比某些直接转换的方式效率低。


3
如果您需要精确的浮点数计算,请使用内置的decimal模块。 - Peter DeGlopper
2
只有在需要精确表示终止小数分数时,使用 decimal 才有帮助。它在表示三分之一或更糟的 sqrt(2) 时并不比二进制浮点数更好。 - Patricia Shanahan
嗯...四舍五入比截断更少的“强制转换”是怎么回事呢? - SingleNegationElimination
round() 生成一个浮点数,然后必须转换为 int - watsonic
一些背景信息可能会有用:pq 的精确整数倍的信息从哪里来?这些是财务数据吗? - Mark Dickinson
显示剩余3条评论
4个回答

3

在问题的评论中提出了一个想法,这里介绍一种通过decimal解决的方法:

from decimal import Decimal

p = .0014
q = .00002

quotient = int(Decimal(str(p)) / Decimal(str(q)))

这当然会导致70

请注意,通过字符串进行转换似乎是必要的,因为:

>>>print decimal.Decimal(8.4)
8.4000000000000003552713678800500929355621337890625

相比之下

>>>print decimal.Decimal(str(8.4))
8.4

1
请注意,str 函数会自行进行一些舍入操作,我认为它大约保留了 12 位小数,而浮点数本身则具有 15 位小数。 - Mark Ransom
3
如果pq不是短小精确的十进制数字,这种方法将无法帮助你。例如,请尝试使用p = 4.0/3q = 2.0/3。在Python 2中,使用此方法将得到1。实际上,如果您知道分子接近于分母的整数倍,而且这个整数不是很大,那么int(round(p/q))是迄今为止最简单的解决方法。 - Mark Dickinson
@MarkDickinson:我将在我的问题中澄清,这些数字受到限制,具有某些属性(包括精确的十进制表示),与我的示例相对应。 - watsonic
@MarkDickinson 我被你所说的所吸引。从我所看到的来看,似乎使用Decimal或Fraction模块来得出我知道应该是完美整数的东西太过于笨重了(因为p是q的倍数)。 - watsonic

2
浮点数除法将在分子是分母的倍数且商可以准确表示时给出准确答案。因此,如果您要做的就是将上面的数除以下面的数,那么这是安全的 然而,通常情况下,您正在使用已从十进制转换或某些计算结果的数字。在这些情况下,您需要确定计算中可能发生的误差量(相对误差1.11e-16是从十进制转换的安全选择,除非数字非常小),并在转换为整数之前按比例缩放结果。
也就是说,当topbot处于合理范围内时,int((top / bot) * (1 + 2.22e-16))应该能够实现您想要的效果。

你对第一个关于精确答案的说法有参考资料吗?我想看一些声明在所有情况下都是正确的东西... - watsonic
@watsonic:IEEE规范指定除法进行正确舍入。这意味着我回答中的第一句话。 - tmyklebu
2
我认为你的第一个陈述是错误的,或者至少是误导性的。我可以用最简单的例子来反驳它,就是将 0.3 除以 0.1。不过你最后的建议是合理的。 - Mark Ransom
@watsonic:你没有确认他的陈述。你正在对四舍五入的值进行算术运算。 - tmyklebu
@tmyklebu 0.3 的计算结果为 0.2999999999999999888977697537484345957636833190917968750,而 0.1 的计算结果为 0.1000000000000000055511151231257827021181583404541015625 - Mark Ransom
显示剩余8条评论

2

在你进行除法运算之前,你可以使用Decimal或Fraction对象来处理这些内容。但是,在进行除法计算时,Python提供了一个相应的模块:

>>> import fractions
>>> fractions.Fraction(.0014/.00002)
Fraction(70, 1)
>>> int(fractions.Fraction(2.3))
2
>>> int(fractions.Fraction(8.35))
8

但是经过仔细阅读您的问题后,我认为您的担忧并没有必要。如果您试图想出一个分数,在这个分数由于四舍五入误差而低于一个整数,而如果您能够进行更高精度的计算,则该分数将高于该整数,那么您将无法找到这样的分数。
例如,以下数字的分数永远不会四舍五入低于1:
>>> fractions.Fraction(1.000000000000001)
Fraction(4503599627370501, 4503599627370496)

在评论中,有人建议得出的股息与1.64相差甚远。他没有说明如何得出这个数值,但正如我在介绍中所说,如何计算到分配点是由您决定的。


我在考虑这个例子:https://dev59.com/o2kw5IYBdhLWcg3wQ4dd#10011589,因此类比地想象一下,如果 p = .0014 被表示为略低于 .0014,而 q = .00002 稍微大于那个数字。商将略小于70,因此当转换时会变成69。 - watsonic
很容易找到分子舍入略小于实际值,而分母舍入略大的例子。我不认为在这种情况下除法保证向上舍入。例如0.0164变成了0.01619999999999999912292381054612633306533098220825195,而0.0054变成了0.00540000000000000028588242884097780915908515453338623;尝试除以它们,看看你得到什么。 - Mark Ransom
@MarkRansom,请向我们展示导致舍入误差的过程。 - Russia Must Remove Putin
Aaron:在提示符下尝试 int(fractions.Fraction(0.0162 / 0.0054))。(我认为@MarkRansom的评论中有一个错别字:他可能是指 0.0162 而不是 0.0164。) - Mark Dickinson
@MarkDickinson 是的,那是个笔误,谢谢指出。我的方法甚至更简单,只需 '%0.53f' % 0.0162,尽管事后我需要打印更多数字。至于如何找到可能成为问题的数字组合,我只是使用了一个简单的 for 循环。 - Mark Ransom

1

看起来最简单的解决方案是四舍五入为整数:

int(round(p/q))

也许在行内附加一个简短的注释,指出pq的倍数,以避免错误地暗示p/q与整数有可能存在较大差距。

请注意,此解决方案保证完全安全,因为此处通过int()进行的强制转换作用于浮点数,该浮点数将是整数的精确表示, 由round()返回。根据IEEE双精度浮点标准,Python float符合这种精确性,最高可达253

它可能比其他方法(如问题中的建议)微不足道地低效,但肯定比通过decimalfractions模块处理更有效率。并且可能与采用额外乘法和加法的其他解决方案相当。


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