HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

Converting a double to a std::string without scientific notation

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
stdwithoutscientificdoublenotationconvertingstring

Problem

Problem

I need to convert a double to a std::string which contains no scientific notation and no unnecessary fillers.

Using std::fixed with iostream yielded ugly results - extra fillers (0's). The closest working code I could get was to using sprintf("%g",...) and then dealing with exponentials, should they appear (beyond 6 places).

I am looking for improvements in robustness or clarity/simplicity in the implementation.

Details/Elaboration (addition)

I realize that the conversion between double and std::string and back can be expected to be lossy. So I am not looking for an implementation that works for all cases. For example, I don't expect to be able to convert "0.2000" to double and then get the same back (getting "0.2" back would be fine). Similarly with "+0.2000": getting "0.2" back is ok.

I have attempted solutions involving sprintf("%g",...), but the result is automatic conversion to scientific notation when we go beyond six significant digits. There appears to be no way to configure that setting. Alternatively, sprintf("%f",...) requires that we manually set or calculate the exact precision that we want - which I don't know how to do if that information is lost inside a double. Note that what I have found here seems to equally apply to most simple solutions involving std::iostream and std::fixed.

```
#include
#include
#include
#include

static std::string
to_string( double d )
{
char s[64];
sprintf( s, "%g", d );

char* e = std::strchr( s, 'e' );
if ( e==nullptr )
return s;
char* p = std::strchr( s, '.' );

// eg 2.4e-07
int exp = atoi( e+1 ); // -7
int extra = 0;

// snip period eg 24
if ( p!=nullptr ) {
extra = e-p-1;
memmove( p, p + 1, strlen(s) - (p-s));
}

int coeff = atoi( s );
// std::cerr << "DEBUG = " << s << " coeff = " << coeff << " exp = " << exp << "\n";
if ( exp<0 ) {
sprintf( s, "0.%0*d", -exp+extra, coeff);
return s;
}

// eg 7.3e+09

Solution

As mentioned in the comments of the other answer, your decimal floating point numbers are stored as binary and rarely have finite representations.

If you simply want code that passes your test I would mention that it is much easier to remove padding from std::fixed than it is to expand the scientific notation.

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

static std::string to_string(double d)
{
    std::ostringstream oss;
    oss.precision(std::numeric_limits::digits10);
    oss << std::fixed << d;
    std::string str = oss.str();

    // Remove padding
    // This must be done in two steps because of numbers like 700.00
    std::size_t pos1 = str.find_last_not_of("0");
    if(pos1 != std::string::npos)
        str.erase(pos1+1);

    std::size_t pos2 = str.find_last_not_of(".");
    if(pos2 != std::string::npos)
        str.erase(pos2+1);

    return str;
}

static void test_string_conversion(const std::string& str)
{
    assert( to_string( atof(str.c_str()) ) == str );
}

int main(void)
{
    test_string_conversion( "1.5203" );
    test_string_conversion( "-1.025" );
    test_string_conversion( "3.1415" );
    test_string_conversion( "-38271" );
    test_string_conversion( "0.8382" );
    test_string_conversion( "-0.8382" );
    test_string_conversion( "7" );
    test_string_conversion( "-3" );
    test_string_conversion( "0.0000024" );
    test_string_conversion( "7300000000" );
}

Code Snippets

#include <iostream>
#include <string>
#include <sstream>
#include <limits>
#include <iomanip>
#include <cstdlib>
#include <cassert>
#include <cstddef>

static std::string to_string(double d)
{
    std::ostringstream oss;
    oss.precision(std::numeric_limits<double>::digits10);
    oss << std::fixed << d;
    std::string str = oss.str();

    // Remove padding
    // This must be done in two steps because of numbers like 700.00
    std::size_t pos1 = str.find_last_not_of("0");
    if(pos1 != std::string::npos)
        str.erase(pos1+1);

    std::size_t pos2 = str.find_last_not_of(".");
    if(pos2 != std::string::npos)
        str.erase(pos2+1);

    return str;
}


static void test_string_conversion(const std::string& str)
{
    assert( to_string( atof(str.c_str()) ) == str );
}


int main(void)
{
    test_string_conversion( "1.5203" );
    test_string_conversion( "-1.025" );
    test_string_conversion( "3.1415" );
    test_string_conversion( "-38271" );
    test_string_conversion( "0.8382" );
    test_string_conversion( "-0.8382" );
    test_string_conversion( "7" );
    test_string_conversion( "-3" );
    test_string_conversion( "0.0000024" );
    test_string_conversion( "7300000000" );
}

Context

StackExchange Code Review Q#90565, answer score: 4

Revisions (0)

No revisions yet.