user152949
user152949

Reputation:

sprintf too many/few decimals

I must convert decimal numbers using a non-scientific (ie. no mantissa/exponent/E's) character string. My code looks like this:

/*!
\brief Converts a <em>XML Schema Decimal</em>
*/
char *ToDecimal(double val) const
{
    const size_t nMax = 200;
    char *doubleStr = new char[nMax];

    sprintf(doubleStr, "%1.6f", val);

    return doubleStr;
}

The problem is that when the input val is 1 then the function returns 1.000000 but I was hoping for a output of 1. Also, if I change the code to sprintf(doubleStr, "%1.0f", val); then it correctly outputs 1, but if the input val is changed to 0.000005 the output is 0, and I was hoping the output would then be 0.000005. So basically I want all output to be as short as possible and remove all unnessesary 0's. Is this possible with sprintf? I would like to support 3.4E +/- 38 range with this function.

Upvotes: 4

Views: 628

Answers (2)

anatolyg
anatolyg

Reputation: 28251

It turns out that c++ iostreams (specifically, ostringstream) are better suited for your task than sprintf.

Use the std::fixed manipulator to disable scientific notation. Use std::setprecision to specify precision (number of characters after decimal dot). In your case, precision of 45 places seems enough to represent all float numbers.

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

std::string convert(double x)
{
    std::ostringstream buffer;
    buffer << std::fixed << std::setprecision(45) << x;
    std::string result = buffer.str();

    return result;
}

In addition, to clean-up the result, remove any trailing zeros.

    size_t i = result.find_last_not_of('0');
    if (result[i] != '.')
        ++i;
    result.erase(i);

Note: the clean-up of trailing zeros will only work for numbers that are exactly representable (like 0.75 or 0.03125): for example, the number 0.1 is converted to 0.10000000000000000555111512312578270211815834. One could use non-constant precision (depending on the magnitude of the number), but this is very tricky to get right.

Instead, it's possible to use the following ugly (and slow) hack: try converting the start of the string back to double, and cut the string if the result is equal to the initial number.

size_t i;
for (i = 1; i < result.size(); ++i)
{
    std::istringstream cut(result.substr(0, i));
    double temp;
    cut >> temp; // the verbose syntax could fit into one line 
    if (temp == x) // by using boost::lexical_cast
        break;
}

Upvotes: 1

Cassio Neri
Cassio Neri

Reputation: 20513

Since this is tagged C++, I assume you can use C++ features that are not available in C (if not, tell me and I'll delete this answer).

First, I suggest using a std::string instead of char* (this frees you from memory managing the buffer)? Second, I suggest using a ostringstream for the conversion:

#include <sstream>

std::string ToDecimal(double val) {
    std::ostringstream oss;
    oss << val;
    return oss.str();
}

Upvotes: 1

Related Questions