为什么 QDoubleSpinBox 的精度有时会高于其小数 属性 所允许的精度?

Why is the precision of a QDoubleSpinBox sometimes higher than its decimals property allows?

在 PySide2 中,如果我创建一个 QDoubleSpinBox 并将其 decimals-属性 显式设置为 2 并将其 singleStep-属性 显式设置为 0.1,然后更改其值通过 up/down 按钮,打印值有时具有更高的精度。

为什么会这样?

虽然使用此值进行计算不一定是问题,但它会影响用户体验:

在我的例子中,我想在 spinbox-value 为 0.0 时禁用 QPushButton,否则启用它。如果旋转框值变得非常接近 0,按钮(以及旋转框的向下箭头)不会被禁用,尽管旋转框内的可读数字显示为“0,00”。


我目前为改善用户体验所做的工作是:

int(self.spin.value()*100) > 0

而不是:

self.spin.value() > 0.0

这真的是最好的方法吗?


这是一个重现不良行为的小例子:

import sys
from PySide2 import QtWidgets

class Window(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super(Window, self).__init__(parent)
        self.spin = QtWidgets.QDoubleSpinBox(self)
        self.spin.setDecimals(2)
        self.spin.setSingleStep(0.1)
        self.button = QtWidgets.QPushButton(self)
        self.button.move(0, 40)
        self.spin.valueChanged.connect(self.on_spin_value_change)
        self.show()

    def on_spin_value_change(self, value):
        print(value)
        self.button.setEnabled(value > 0.0)


app = QtWidgets.QApplication(sys.argv)
window = Window()
sys.exit(app.exec_())

这是一个示例输出:


编辑: 不,我的问题不能简单地归结为 "how to compare floats in Python"。我要求一种方法来修复 QDoubleSpinBoxes 的用户体验,并解释为什么值的内部表示与可见的不同。是的,选择不同的方式来比较浮点数 可以 解决问题,但使用另一个重载信号对我来说是更好的解决方案 - 如果我的问题不会被告知我的解决方案' Qt/PySide-specific.

问题是浮点数在计算机内存中的表示 wikipedia

添加到您的代码 from math import isclose 并更改按钮检查 self.button.setEnabled(not isclose(0, value, abs_tol=1e-9))

没有数学的其他解决方案是使用 abs 并将值与一些非常小的数字进行比较。 self.button.setEnabled(abs(value) > 1e-9)

更好的方法(“最佳”,我不知道)是将插槽 on_spin_value_change 连接到通过 [=16= 的(过载)valueChanged 信号] 参数作为实际字符串,而不是浮点数。

请注意 Qt 实际上是如何定义两个具有不同签名的信号的。您使用 passes the value as a number 并具有此 (C++) 签名的那个:

void QDoubleSpinBox::valueChanged(double d)

而另一个 passes the value as a string:

void QDoubleSpinBox::valueChanged(const QString &text)

(编辑添加:同时,更新的 Qt 5 版本和 Pyside2 as well, have deprecated that second, overloaded signal in favor of the more aptly named textChanged。)

为了连接到后者,而不是第一个,请使用此语法(在 Python 中):

self.spin.valueChanged[str].connect(self.on_spin_value_change)

然后改变插槽中的条件,利用它现在接收字符串的事实:

self.button.setEnabled(float(value) > 0)

这将起作用,因为 float('0.0…') 对字符串表示中任意数量的零求值为真零。或者,我们也可以使用字符串操作来评估条件。

我同意 所说的,因为从概念上讲,使用 math 是正确的(恕我直言 actually更优雅)的方式。

另一种选择是使用 round 函数,因为我们知道基于旋转框 decimals 属性 的精度:

    self.button.setEnabled(round(value, self.spin.decimals()))

这个解决方案是给定方案之间的中间方案,但我相信它也可能是一个更有效的方案:

  • 确实使用数学而不是字符串转换;
  • 不需要math模块;导入它并不是一种开销(特别是与 Qt 模块相反,考虑到甚至一些内置函数实际上仍然使用它),但人们可能更愿意避免它;
  • 它比 1e-9;
  • 的任何用法都更具可读性
  • 它仍然考虑实际显示的值(因为低级实现可能是相同的),所以 -0.00 仍然返回为 -0.00,但它也等于 0(参见 negative zero in python);