How to 'cout' the correct number of decimal places of a double value?
Asked Answered
S

9

28

I need help on keeping the precision of a double. If I assign a literal to a double, the actual value was truncated.

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

For the above code snippet, the output was 7.402
Is there a way to prevent this type of truncation? Or is there a way to calculate exactly how many floating points for a double? For example, number_of_decimal(x) would give 11, since the input is unknown at run-time so I can't use setprecision().


I think I should change my question to: How to convert a double to a string without truncating the floating points. i.e.

#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";
}

The expected output should be 7.40200 but the actual result was 7.402. So how can I work around this problem? Any idea?

Sidelong answered 18/11, 2010 at 17:2 Comment(2)
See this for floating point precision.Lati
"The expected output" -- the problem is with your expectations. "7.40200 but the actual result was 7.402" -- those are equal, so nothing was "truncated". cout can't possibly know how many zeroes you typed into your source file.Vaal
V
34

Due to the fact the float and double are internally stored in binary, the literal 7.40200133400 actually stands for the number 7.40200133400000037653398976544849574565887451171875

...so how much precision do you really want? :-)

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

And yes, this program really prints 7.40200133400000037653398976544849574565887451171875!

Vannessavanni answered 18/11, 2010 at 18:36 Comment(9)
Hi Fred, I don't want any specified precision. I just want convert it to a string as its original format. Correct me if I misunderstood your instruction. Thanks,Sidelong
@Chan: Do you want 7.402001334 or 7.40200133400 or 7.402001334000000?Vannessavanni
I want 7.40200133400, no more no less.Sidelong
@Chan: Why? Because you wrote 7.40200133400 in the source file? That's impossible, because to the compiler, the literals 7.402001334, 7.40200133400 and 7.402001334000000 are equivalent. They all yield the same value which is then stored in x. There is no additional information about how many zeros you wrote down at the end.Vannessavanni
@Fred: Hm, it is more complicated than I initially thought. So like you said. Is it impossible to do so?Sidelong
@Chan: If all you want is to read a number from a text file and then print it (without doing any calculations), why don't you just read it as a std::string instead of a double?Vannessavanni
−1 Re "float and double are internally stored in binary", no not necessarily. The standard allows decimal representation for floats. Re "stands for the number 7.40200133400000037653398976544849574565887451171875", no not with any common representation. E.g. 64-bit IEEE 754 has 16 to 17 significant decimal digits. And it's no problem to present numbers in a reasonable way: most script languages manage to do that.Tracheo
I added an in my view reasonable answer.Tracheo
C++ double has 15-17 decimal digits, float has half (approx 7).Swamper
B
20

You must use setiosflags(ios::fixed) and setprecision(x).

For example, cout << setiosflags(ios::fixed) << setprecision(4) << myNumber << endl;

Also, don't forget to #include <iomanip.h>.

Besse answered 18/11, 2010 at 17:20 Comment(1)
+1 for ios::fixed as: setiosflags(ios::fixed) helps for the case when num == (int)num, allows to output number in format like 2.00, without it output will be 2Beery
C
11
std::cout << std::setprecision(8) << x;

Note that setprecision is persistent and all next floats you print will be printed with that precision, until you change it to a different value. If that's a problem and you want to work around that, you can use a proxy stringstream object:

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

For more info on iostream formatting, check out the Input/output manipulators section in cppreference.

Campbellbannerman answered 18/11, 2010 at 17:9 Comment(2)
Hi Kos, I understand what you said, but the problem was the precision is changing from one value to another. Eg: 1.233 and 3.99945, one has 3 and one has 5.Sidelong
My bad! The floating points are different not the precisions.Sidelong
O
5

Solution using Boost.Format:

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

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

This outputs 7.4020013340000004.

Hope this helps!

Offing answered 18/11, 2010 at 17:13 Comment(2)
First, thanks Daniel. But I think the boost::format behaves exactly like cout.set( 0, std::ios::floatfield ), and your output is not the original one. My problem is I want to convert that double value to a string.Sidelong
The original one is just something you've typed into a text file. It does not necessarily represent the actual value.Thrombin
D
3

The only answer to this that I've come up with is that there is no way to do this (as in calculate the decimal places) correctly! THE primary reason for this being that the representation of a number may not be what you expect, for example, 128.82, seems innocuous enough, however it's actual representation is 128.8199999999... how do you calculate the number of decimal places there??

Deliciadelicious answered 18/11, 2010 at 17:17 Comment(4)
You can add or remove 0.0000000001 to it, then print it again and see if the new output is using less characters. The amount to add or remove is dependent on the precision you are happy with.Thrombin
Unless you're truly worried about that level of precision, in which case you're likely doing your own thing, you just round to 15 places. After 15 you don't have very good precision anyway.Homogamy
In fact, I want to convert this value to a string, and I want to keep its format as it was read from a file.Sidelong
Your variables are stored in binary. Decimal finite-representation numbers may have non-finite-representation in binary. The representation is finite if and only if your number is a sum of powers of 2.Lati
M
2

Responding to your answer-edit: There is no way to do that. As soon as you assign a value to a double, any trailing zeroes are lost (to the compiler/computer, 0.402, 0.4020, and 0.40200 are the SAME NUMBER). The only way to retain trailing zeroes as you indicated is to store the values as strings (or do trickery where you keep track of the number of digits you care about and format it to exactly that length).

Mahala answered 18/11, 2010 at 19:44 Comment(0)
M
1

Let s make an analogous request: after initialising an integer with 001, you would want to print it with the leading zeroes. That formatting info was simply never stored.

For further understanding the double precision floating point storage, look at the IEEE 754 standard.

Meretricious answered 5/4, 2013 at 13:21 Comment(0)
M
1

Doubles don't have decimal places. They have binary places. And binary places and decimal places are incommensurable (because log2(10) isn't an integer).

What you are asking for doesn't exist.

Moise answered 23/5, 2013 at 7:39 Comment(0)
T
1

The second part of the question, about how to preserve trailing zeroes in a floating point value from value specification to output result, has no solution. A floating point value doesn't retain the original value specification. It seems this nonsensical part was added by an SO moderator.

Regarding the first and original part of the question, which I interpret as how to present all significant digits of 7.40200133400, i.e. with output like 7.402001334, you can just remove trailing zeroes from an output result that includes only trustworthy digits in the double value:

#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";
}

Output:

7.402001334
0.000000000007402001334
128.81999999999999

You might consider adding logic for rounding to avoid long sequences of 9's, like in the last result.

Tracheo answered 12/4, 2018 at 10:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.