Как правильно вычислить правильное количество десятичных разрядов двойного значения? - PullRequest
26 голосов
/ 18 ноября 2010

Мне нужна помощь в сохранении точности double.Если я назначу литерал двойному, фактическое значение будет усечено.

int main() {
    double x = 7.40200133400;
    std::cout << x << "\n";
}

Для приведенного выше фрагмента кода будет получено 7.402
Есть ли способ предотвратить этот тип усечения?Или есть способ точно рассчитать, сколько чисел с плавающей запятой для double?Например, number_of_decimal(x) даст 11, так как ввод неизвестен во время выполнения, поэтому я не могу использовать setprecision().


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

#include <iostream>
#include <string>
#include <sstream>

template<typename T>
std::string type_to_string( T data ) {
    std::ostringstream o;
    o << data;
    return o.str();
}

int main() {
    double x = 7.40200;
    std::cout << type_to_string( x ) << "\n";
}

Ожидаемый результат должен быть 7.40200, но фактический результат был 7.402.Так как я могу обойти эту проблему?Есть идеи?

Ответы [ 9 ]

28 голосов
/ 18 ноября 2010

Ввиду того, что float и double внутренне хранятся в двоичном виде, литерал 7.40200133400 фактически означает число 7.40200133400000037653398976544849574565887451171875

... так какую точность вы действительно хотите?: -)

#include <iomanip>    
int main()
{
    double x = 7.40200133400;
    std::cout << std::setprecision(51) << x << "\n";
}

И да, эта программа действительно печатает 7.40200133400000037653398976544849574565887451171875!

18 голосов
/ 18 ноября 2010

Вы должны использовать setiosflags(ios::fixed) и setprecision(x).

Например, cout << setiosflags(ios::fixed) << setprecision(4) << myNumber << endl;

Кроме того, не забудьте #include <iomanip.h>.

9 голосов
/ 18 ноября 2010
std::cout << std::setprecision(8) << x;

Обратите внимание, что setprecision является постоянным, и все последующие распечатки будут печататься с такой точностью, пока вы не измените его на другое значение. Если это проблема, и вы хотите обойти ее, вы можете использовать прокси stringstream объект:

std::stringstream s;
s << std::setprecision(8) << x;
std::cout << s.str();

Дополнительную информацию о форматировании iostream можно найти в разделе Манипуляторы ввода / вывода в cppreference.

5 голосов
/ 18 ноября 2010

Решение с использованием Boost.Format :

#include <boost/format.hpp>
#include <iostream>

int main() {
    double x = 7.40200133400;
    std::cout << boost::format("%1$.16f") % x << "\n";
}

Это выводит 7.4020013340000004.

Надеюсь, это поможет!

3 голосов
/ 18 ноября 2010

Единственный ответ на этот вопрос, который я придумал, заключается в том, что нет способа сделать это (как при вычислении десятичных знаков) правильно! Основная причина этого в том, что представление числа может быть не тем, что вы ожидаете, например, 128.82, кажется достаточно безобидным, однако его фактическое представление - 128.8199999999 ... как вы вычисляете количество десятичных знаков там ?? 1001 *

2 голосов
/ 23 мая 2013

В парном разряде нет десятичных знаков.У них есть двоичные места.А двоичные и десятичные разряды несопоставимы (потому что log2(10) не является целым числом).

То, что вы просите, не существует.

2 голосов
/ 18 ноября 2010

Отвечая на ваш ответ-редактировать: нет никакого способа сделать это.Как только вы присваиваете значение double, все завершающие нули теряются (для компилятора / компьютера 0,402, 0,4020 и 0,40200 - это ОДИНАКОВОЕ НОМЕР).Единственный способ сохранить конечные нули, как вы указали, - сохранить значения в виде строк (или выполнить хитрость, где вы отслеживаете количество нужных вам цифр и форматируете его точно до этой длины).

1 голос
/ 05 апреля 2013

Давайте сделаем аналогичный запрос: после инициализации целого числа с 001 вы захотите напечатать его с ведущими нулями.Эта информация о форматировании просто никогда не сохранялась.

Для более глубокого понимания системы хранения данных с плавающей запятой двойной точности обращайтесь к стандарту IEEE 754.

0 голосов
/ 12 апреля 2018

Вторая часть вопроса о том, как сохранить конечные нули в значении с плавающей запятой от спецификации значения до результата вывода, не имеет решения. Значение с плавающей запятой не сохраняет первоначальную спецификацию значения. Кажется, что эта бессмысленная часть была добавлена ​​модератором SO.

Что касается первой и исходной части вопроса, которую я интерпретирую как то, как представить все значащие цифры 7.40200133400, т. Е. С выводом, подобным 7.402001334, вы можете просто удалить конечные нули из результата вывода, который включает в себя только достоверные цифры в значении double:

#include <assert.h>         // assert
#include <limits>           // std::(numeric_limits)
#include <string>           // std::(string)
#include <sstream>          // std::(ostringstream)

namespace my{
    // Visual C++2017 doesn't support comma-separated list for `using`:
    using std::fixed; using std::numeric_limits; using std::string;
    using std::ostringstream;

    auto max_fractional_digits_for_positive( double value )
        -> int
    {
        int result = numeric_limits<double>::digits10 - 1;
        while( value < 1 ) { ++result; value *= 10; }
        return result;
    }

    auto string_from_positive( double const value )
        -> string
    {
        ostringstream stream;
        stream << fixed;
        stream.precision( max_fractional_digits_for_positive( value ) );
        stream << value;
        string result = stream.str();
        while( result.back() == '0' )
        {
            result.resize( result.size() - 1 );
        }
        return result;
    }

    auto string_from( double const value )
        -> string
    {
        return (0?""
            : value == 0?   "0"
            : value < 0?    "-" + string_from_positive( -value )
            :               string_from_positive( value )
            );
    }
}

#include<iostream>
auto main()
    -> int
{
    using std::cout;
    cout << my::string_from( 7.40200133400 ) << "\n";
    cout << my::string_from( 0.00000000000740200133400 ) << "\n";
    cout << my::string_from( 128.82 ) << "\n";
}

Выход:

7.402001334
0.000000000007402001334
128.81999999999999

Вы можете рассмотреть возможность добавления логики для округления, чтобы избежать длинных последовательностей из 9, как в последнем результате.

...