【问题标题】:Python - safe conversion of quotient of floats to intPython - 将浮点商安全转换为int
【发布时间】:2014-07-20 20:41:36
【问题描述】:

在我的情况下,我试图将一个浮点数 p 除以另一个 q。顶部是底部的倍数,并且都具有以下属性:

  1. 完全可以用十进制表示
  2. 有效数字最多为 3 或 4 位
  3. 介于11e-8 之间。

(想想,例如,p=.0014q=.00002

在一个完美的世界中,除法会得到一个完美的整数(这里是 70)。但是浮点运算是often imperfect

我想要一种最简单、最安全、最有效的方法,以避免在将商转换为 int 时返回 p/q - 1 的错误。

我现在最好的解决方案是这样做:

int(p/q + 1e-10)

但这感觉不干净,而且效率可能比可能的低。

另外,我知道我可以四舍五入,但这在代码中似乎具有误导性,并且可能比某种直接转换效率低。

【问题讨论】:

  • 如果您需要精确的浮点数学运算,请使用内置的decimal 模块。
  • 当且仅当您需要精确表示终止小数时,使用 decimal 会有所帮助。它在代表三分之一方面并不比二进制浮点好,或者更糟的是 sqrt(2)。
  • hmm.. 四舍五入比截断少了多少?
  • round() 生成一个浮点数,然后必须将其强制转换为int
  • @PeterDeGlopper:我在下面实施了您的建议。它不是超级漂亮,但在我概述的情况下它可能是最安全的?这与 Aaron 给出的分数答案相比如何?

标签: python casting floating-point


【解决方案1】:

在进行除法时如何处理这些问题取决于您自己。到目前为止,也许您应该使用 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。他是如何得出的,他没有说,但正如我在介绍中所说,你如何计算到除法点取决于你。

【讨论】:

  • 我在想这个例子:stackoverflow.com/a/10011589/695804 所以通过类比假设p = .0014 表示为略低于 0.0014,q = .00002 略大于该数字。商将略小于 70,因此当施法时最终为 69
  • 很容易找到分子四舍五入略小于实际值而分母四舍五入略多的示例。在这种情况下,我不认为该部门可以保证收官。例如0.0164变成0.016199999999999999122923810546126333065330982208251950.0054变成0.00540000000000000028588242884097780915908515453338623;试着把它们分开,看看你会得到什么。
  • @MarkRansom 请向我们展示导致该舍入错误的过程。
  • Aaron:在提示符下尝试int(fractions.Fraction(0.0162 / 0.0054))。 (我认为@MarkRansom 的评论中有一个错字:我怀疑他的意思是0.0162 而不是0.0164。)
  • @MarkDickinson 是的,这是一个错字,谢谢。而且我的方法更简单,它只是'%0.53f' % 0.0162,尽管事后我需要打印更多的数字。至于我是如何找到会成为问题的数字组合的,我只是使用了一个简单的 for 循环。
【解决方案2】:

如果分子是分母的倍数并且商可以精确表示,则浮点除法将给出准确的答案。因此,将顶部除以底部是安全的如果您正在尝试这样做

但是,您通常会处理从十进制转换而来的数字,或者是某些计算的结果。在这些情况下,您需要弄清楚计算中可能发生多少错误(1.11e-16 的相对错误是从十进制转换的安全赌注,除非数字非常小)并在转换为整数之前将结果按比例放大.

也就是说,当topbot 在合理范围内时,int((top / bot) * (1 + 2.22e-16)) 应该做你想做的事。

【讨论】:

  • 您的第一个关于确切答案的断言是否有参考资料?我想看看在所有情况下这都是真的……
  • @watsonic:IEEE 规范指定除法是正确舍入的。这暗示了我回答的第一句话。
  • 如果您可以提供对规范段落/部分的参考以确认这一点,我很乐意接受您的回答
  • 我认为您的第一个陈述是错误的,或者至少具有误导性。我可以用来反驳它的最简单的例子是将0.3 除以0.1。不过你最后的建议是合理的。
  • @watsonic:IEEE 754-1985 第 5 节的第一段明确指出加法、减法、乘法、除法和平方根是正确舍入的。
【解决方案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

【讨论】:

  • 请注意,str 会自行进行一些舍入,我认为它是小数点后 12 位,而浮点数是 15 位。
  • 如果pq 不是源自具有简短、精确的十进制表示的数字,这将无济于事。例如,试试p = 4.0 / 3q = 2.0 / 3。在 Python 2 上,您将使用此方法获得 1。确实,如果您知道分子接近分母的整数倍,并且该整数并不大,int(round(p / q)) 是迄今为止最简单的解决方案。
  • @MarkDickinson:我将在我的问题中澄清这些数字被限制为具有与我的示例相对应的某些属性(包括精确的十进制表示)。
  • @MarkDickinson 我很想从我所看到的情况中认为你是正确的。使用 Decimal 或 Fraction 模块来得出我知道应该是完美整数的东西似乎过于笨拙(因为 p 是 q 的倍数)。
【解决方案4】:

目前看来,最直接的解决方案是四舍五入:

int(round(p/q))

可能伴随着一个简短的注释,指出pq 的倍数,以避免任性暗示p/q 与整数有潜在的显着距离。

请注意,此解决方案保证绝对安全,因为在这种情况下通过int() 进行的转换作用于an exact representation of an integer 的浮点数,由round() 返回。根据 Python float 所遵循的双精度 IEEE 浮点标准,此精确度最高可保证 253

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

【讨论】:

    猜你喜欢
    • 2022-08-02
    • 2014-09-03
    • 1970-01-01
    • 1970-01-01
    • 2021-09-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-17
    相关资源
    最近更新 更多