为什么 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"。我要求一种方法来修复 QDoubleSpinBox
es 的用户体验,并解释为什么值的内部表示与可见的不同。是的,选择不同的方式来比较浮点数 可以 解决问题,但使用另一个重载信号对我来说是更好的解决方案 - 如果我的问题不会被告知我的解决方案' 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);
在 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"。我要求一种方法来修复 QDoubleSpinBox
es 的用户体验,并解释为什么值的内部表示与可见的不同。是的,选择不同的方式来比较浮点数 可以 解决问题,但使用另一个重载信号对我来说是更好的解决方案 - 如果我的问题不会被告知我的解决方案' 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…')
对字符串表示中任意数量的零求值为真零。或者,我们也可以使用字符串操作来评估条件。
我同意
另一种选择是使用 round
函数,因为我们知道基于旋转框 decimals
属性 的精度:
self.button.setEnabled(round(value, self.spin.decimals()))
这个解决方案是给定方案之间的中间方案,但我相信它也可能是一个更有效的方案:
- 它确实使用数学而不是字符串转换;
- 不需要
math
模块;导入它并不是一种开销(特别是与 Qt 模块相反,考虑到甚至一些内置函数实际上仍然使用它),但人们可能更愿意避免它; - 它比
1e-9
; 的任何用法都更具可读性
- 它仍然考虑实际显示的值(因为低级实现可能是相同的),所以
-0.00
仍然返回为-0.00
,但它也等于0
(参见 negative zero in python);