Почему точность QDoubleSpinBox иногда выше, чем позволяет его свойство decimals?

В PySide2, если я создаю QDoubleSpinBox и явно устанавливаю его свойство decimals равным 2, а его свойство singleStep равным 0,1, а затем изменяю его значение с помощью кнопок вверх/вниз, напечатанное значение иногда имеет гораздо более высокую точность.

Почему это так?

Хотя это не обязательно может быть проблемой для вычислений, выполняемых с этим значением, это может повлиять на работу пользователя:

В моем случае я хочу отключить QPushButton, если значение счетчика равно 0,0, и включить его в противном случае. В случае, если значение счетчика становится действительно близким к 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_())

Это пример вывода:

введите здесь описание изображения


Редактировать: Нет, мой вопрос не сводится просто к тому, "как сравнивать числа с плавающей запятой в Python". Я попросил способ исправить взаимодействие с пользователем QDoubleSpinBoxes и объяснить, почему внутреннее представление значения отличается от видимого. Да, выбор другого способа сравнения чисел с плавающей запятой может решить проблему, но использование другого перегруженного сигнала является лучшим решением для меня - решение, о котором мне не сказали бы, если бы мой вопрос не был Зависит от Qt/PySide.


person S818    schedule 31.07.2019    source источник


Ответы (2)


Лучший способ сделать это («лучший», я не знаю) — подключить слот on_spin_value_change к (перегруженному) сигналу valueChanged, который передает параметр value как фактическую строку, а не как число с плавающей запятой.

Обратите внимание, как Qt на самом деле определяет два сигнала с разными сигнатурами. Тот, который вы использовали, передает значение в виде числа и имеет это ( С++) подпись:

void QDoubleSpinBox::valueChanged(double d)

В то время как другой передает значение в виде строки:

void QDoubleSpinBox::valueChanged(const QString &text)

(Это неочевидно из документации PySide2, на данный момент пишу, но API тот же.)

Чтобы подключиться к последнему, а не к первому, используйте этот синтаксис (в Python):

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

Затем измените условие в слоте, используя тот факт, что теперь он получает строку:

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

Это будет работать, потому что float('0.0…') оценивается как истинный нуль для любого количества нулей в строковом представлении.

person John Hennig    schedule 31.07.2019
comment
Большой! И, кстати, вы научили меня, как получить доступ к перегруженным сигналам! - person S818; 01.08.2019

Проблема заключается в представлении чисел с плавающей запятой в памяти компьютера wikipedia.

Добавьте в свой код from math import isclose и измените проверку кнопки на self.button.setEnabled(not isclose(0, value, abs_tol=1e-9))

Другим решением без математики является использование abs и сравнение значения с каким-то очень маленьким числом. self.button.setEnabled(abs(value) > 1e-9)

person Grzegorz Bokota    schedule 31.07.2019
comment
Спасибо, что указали мне на isclose, о котором я не знал! Для этого и потому, что ваш ответ полностью верен, я проголосовал за него, но я принял ответ Джона, потому что он не требует импорта математики и кажется мне немного более элегантным. - person S818; 01.08.2019
comment
Я думаю, что преобразование в строку не является оптимальным решением. Я думаю, что импорт математики создает меньшие накладные расходы, чем преобразование строки в число с плавающей запятой. Добавляю в отзыв второй вариант без библиотеки math. - person Grzegorz Bokota; 02.08.2019