Qt QString :: toDouble () дает неточные результаты

В моем приложении есть следующий код, в котором мне нужно проанализировать строку и преобразовать ее в double

int i = ((QString)"294.4").toDouble() * 100;

Я должен рассматривать значение 29440 как значение i. Однако каждый раз, когда я это делаю, я получаю значение 29439. Я не уверен, почему это происходит, поскольку это происходит не всегда. например 294.1 294.2 294.3 работает нормально. 294.5 тоже нормально работает.

Я понимаю, что интерпретация чисел с плавающей запятой должна быть проблемой, и можно ожидать неопределенного поведения, но этот случай кажется очень простым.

Может ли кто-нибудь посоветовать мне, как надежно анализировать значения, чтобы он всегда давал мне точное значение, представленное его строковым эквивалентом?


person Murtuza Kabul    schedule 16.10.2012    source источник


Ответы (1)


Это не проблема Qt, это вопрос внутреннего представления чисел с плавающей запятой (основанных на базе 2, а не на основе 10). См. Почему бы не использовать Double или Float для представления валюты? для получения хорошей справочной информации.

Если вы полагаетесь на точные значения, не используйте числа с плавающей запятой, а сохраните целое число и дробную часть вашего числа в отдельных целых числах, например

int integ = 294;
int fraction = 4;   /* assumption: one fractional digit */

а затем используйте целочисленную арифметику только для своих расчетов.

См. Есть ли библиотечный класс для представления чисел с плавающей запятой? для ссылок на различные существующие библиотеки для такого рода расчетов.


Основываясь на отзывах из комментариев, я предлагаю разобрать String на целочисленную и дробную части, например, так:

struct Money {
    int integ;
    int fract;
};

Money parseMoney(const QString& input) {
    QString cleanInput = input;
    Money result = {0,0};

    // remove group separators
    QLocale locale = QLocale::system();
    cleanInput.remove(locale.groupSeparator());

    // convert to money
    QStringList parts = cleanInput.split(locale.decimalPoint());
    if (parts.count() == 1) {
       result.integ = parts[0].toInt();
    } else if (parts.count() == 2) {
       result.integ = parts[0].toInt();
       result.fract = parts[1].toInt() * 10;
    } else {
         // error, not a number
    }

    return result;
}

Предполагается, что ввод - это строка, отформатированная в соответствии с текущим языковым стандартом, например. если разделитель групп - ',' десятичная точка - '.' а входная строка - «3294,4», результат в деньгах будет {3294, 40}.

person Andreas Fester    schedule 16.10.2012
comment
Я понимаю. Основная проблема здесь в том, что ценность предоставляет пользователь. В данном случае это валюта и цена чего-то, что я не могу попросить пользователя ввести как целое число. То, что я ищу, - это просто метод надежного анализа значения. После идеального анализа он отлично работает для меня. - person Murtuza Kabul; 16.10.2012
comment
Я также понимаю, что это не проблема Qt, а общая проблема. - person Murtuza Kabul; 16.10.2012
comment
Вы получаете значение в виде строки? - person Andreas Fester; 16.10.2012
comment
именно в этом и проблема. Попытка разобрать строку на удвоение приводит к неточному значению - person Murtuza Kabul; 16.10.2012
comment
В любом случае следует избегать преобразования в тип с плавающей запятой. См. Мой отредактированный ответ для возможного подхода. - person Andreas Fester; 16.10.2012
comment
Вы хотите сказать, что десятичный тип больше подходит для анализа валют? Кроме того, я не использую группы, поэтому для меня это не проблема. Я пробовал тот же пример на C #, php, java, и все они смогли правильно проанализировать значение, за исключением C ++ и, в частности, реализации класса QString toDouble. Я много разбираюсь, и то, что вы предложили, сделает приложение очень неэффективным. Можете ли вы предложить мне подходящий тип данных или метод для непосредственного анализа? - person Murtuza Kabul; 16.10.2012
comment
да. Десятичные типы / типы с фиксированной запятой - единственный допустимый подход при работе с валютами. Никогда не используйте для этого числа с плавающей запятой. Я пробовал ваш образец с Java, и он также печатает мне 29439 вместо 29440 (используемый стандарт IEEE-754 тот же). Конечно, мой пример, приведенный выше, не предназначен для повышения эффективности - его, безусловно, можно сделать более эффективным :-), например путем прямого повторения символов строки в цикле. - person Andreas Fester; 16.10.2012