Double to String Without Scientific Notation or Trailing Zeros, Efficiently

double to string without scientific notation or trailing zeros, efficiently

Before you start, check whether significant time is spent in this function. Do this by measuring, either with a profiler or otherwise. Knowing that you call it a zillion times is all very well, but if it turns out your program still only spends 1% of its time in this function, then nothing you do here can possibly improve your program's performance by more than 1%. If that were the case the answer to your question would be "for your purposes no, this function cannot be made significantly more efficient and you are wasting your time if you try".

First thing, avoid s.substr(0, s.size()-1). This copies most of the string and it makes your function ineligible for NRVO, so I think generally you'll get a copy on return. So the first change I'd make is to replace the last line with:

if(s[s.size()-1] == '.') {
s.erase(s.end()-1);
}
return s;

But if performance is a serious concern, then here's how I'd do it. I'm not promising that this is the fastest possible, but it avoids some issues with unnecessary allocations and copying. Any approach involving stringstream is going to require a copy from the stringstream to the result, so we want a more low-level operation, snprintf.

static std::string dbl2str(double d)
{
size_t len = std::snprintf(0, 0, "%.10f", d);
std::string s(len+1, 0);
// technically non-portable, see below
std::snprintf(&s[0], len+1, "%.10f", d);
// remove nul terminator
s.pop_back();
// remove trailing zeros
s.erase(s.find_last_not_of('0') + 1, std::string::npos);
// remove trailing point
if(s.back() == '.') {
s.pop_back();
}
return s;
}

The second call to snprintf assumes that std::string uses contiguous storage. This is guaranteed in C++11. It is not guaranteed in C++03, but is true for all actively-maintained implementations of std::string known to the C++ committee. If performance really is important then I think it's reasonable to make that non-portable assumption, since writing directly into a string saves copying into a string later.

s.pop_back() is the C++11 way of saying s.erase(s.end()-1), and s.back() is s[s.size()-1]

For another possible improvement, you could get rid of the first call to snprintf and instead size your s to some value like std::numeric_limits<double>::max_exponent10 + 14 (basically, the length that -DBL_MAX needs). The trouble is that this allocates and zeros far more memory than is typically needed (322 bytes for an IEEE double). My intuition is that this will be slower than the first call to snprintf, not to mention wasteful of memory in the case where the string return value is kept hanging around for a while by the caller. But you can always test it.

Alternatively, std::max((int)std::log10(d), 0) + 14 computes a reasonably tight upper bound on the size needed, and might be quicker than snprintf can compute it exactly.

Finally, it may be that you can improve performance by changing the function interface. For example, instead of returning a new string you could perhaps append to a string passed in by the caller:

void append_dbl2str(std::string &s, double d) {
size_t len = std::snprintf(0, 0, "%.10f", d);
size_t oldsize = s.size();
s.resize(oldsize + len + 1);
// technically non-portable
std::snprintf(&s[oldsize], len+1, "%.10f", d);
// remove nul terminator
s.pop_back();
// remove trailing zeros
s.erase(s.find_last_not_of('0') + 1, std::string::npos);
// remove trailing point
if(s.back() == '.') {
s.pop_back();
}
}

Then the caller can reserve() plenty of space, call your function several times (presumably with other string appends in between), and write the resulting block of data to the file all at once, without any memory allocation other than the reserve. "Plenty" doesn't have to be the whole file, it could be one line or "paragraph" at a time, but anything that avoids a zillion memory allocations is a potential performance boost.

How to remove trailing zeros with scientific notation when convert double to string?

At the risk of being heavily downvoted criticised for posting a C answer to a C++ question ... you can use the %lg format specifier in a call to sprintf.

From cpprefernce:

Unless alternative representation is requested the trailing zeros are
removed, also the decimal point character is removed if no fractional
part is left.

So, if you only want to remove the trailing zeros when using scientific notation, you can change your convert_str function to something like the following:

std::string convert_str(double d)
{
if (d >= 0.0001) {
std::stringstream ss;
ss << std::fixed << std::setprecision(4); // I know the precision, so this is fine
ss << d;
return ss.str();
}
else {
char cb[64];
sprintf(cb, "%lg", d);
return cb;
}
}

For the three test cases in your code, this will give:

0.0020
1.23456e-05
1.2e-05

From C++20 and later, the std::format class may offer a more modern alternative; however, I'm not (yet) fully "up to speed" with that, so I cannot present a solution using it. Others may want to do so.

Formatting A Double In A String Without Scientific Notation

Use just %f instead of %.0f.

import java.math.BigDecimal;

public class Main {
public static void main(String[] args) {
double foo = 123456789.1234;
String str = String.format("%f", foo);
System.out.println(str);

// If you want to get rid of the trailing zeros
str = new BigDecimal(str).stripTrailingZeros().toString();
System.out.println(str);
}
}

Output:

123456789.123400
123456789.1234

Print cpp_dec_float in scientific notation without trailing zeros

This turned out to be a tough one.

The short story is: there is no such functionality in cpp_dec_float. What's worse, cpp_dec_float doesn't track the number of significant digits that have been set, so there's no "cheap" way to find the length needed to print the fraction.

Ideas:

  • For some border cases (e.g. 123.000000000000001) one could take the log10 of the reciprocal of the the fractional part + log10 of the integer part. This completely fails to be generically applicable.

  • If you want to use implementation details you might find the 'last inhabited' element in the backend array, and do the maths. However, this is pretty involved (requires modifying cpp_dec_float.hpp and a lot of testing).

  • Finally, I observed that the current implementation for .str() clearly makes zero effort to be efficient. At all.

So all in all I have the following suggestions. Either

  1. switch to the gmp backend (if you can afford it). Note

    • this is not a decimal float representation AFAICT
    • this requires an additional library (libgmp) to be linked
    • gmp_float does have arbitrary precision though, and
    • it's str() implementation does take into account the significance of zeroes in the mantissa

    See it Live On Coliru

    #include <boost/multiprecision/number.hpp>
    #include <boost/multiprecision/gmp.hpp>
    #include <iostream>

    namespace mp = boost::multiprecision;

    int main()
    {
    typedef mp::number<mp::gmp_float<100>> mp_type;
    mp_type test_num("7.071067811865475244008443621048490392848359376884740365883398690000000000000000000e-01");

    std::cout << test_num.str(0, std::ios_base::scientific) << '\n';
    }

    Prints 7.071067811865475244008443621048490392848359376884740365883398690e-01 without further actions required.

  2. If that's not an option, I'd just post-process the output, removing the trailing zeroes:

    template <typename T>
    std::string to_pretty_string(T const& v)
    {
    std::string s = v.str(0, std::ios_base::scientific);
    assert(s.length()>3); // min: 0.e
    switch (s[0])
    { // normalized scientific always has #.####### form of mantissa
    case '-':
    case '+': assert(s[2] == '.'); break;
    default: assert(s[1] == '.'); break;
    }

    auto exp = s.find('e');
    if (std::string::npos != exp && exp > 0)
    {
    for(size_t pos = exp-1; pos; --pos)
    {
    if (s[pos] != '0')
    {
    // remove run of 0s if applicable
    s.erase(pos+1, exp-pos-1);
    break;
    }
    }
    }
    return std::move(s);
    }

See it Live On Coliru again

Formatting n significant digits in C++ without scientific notation

The best way I know (and use it in my own code) is

#include <string>
#include <math.h>
#include <sstream>
#include <iomanip>

int round(double number)
{
return (number >= 0) ? (int)(number + 0.5) : (int)(number - 0.5);
}

std::string format(double f, int n)
{
if (f == 0) {
return "0";
}
int d = (int)::ceil(::log10(f < 0 ? -f : f)); /*digits before decimal point*/
double order = ::pow(10., n - d);
std::stringstream ss;
ss << std::fixed << std::setprecision(std::max(n - d, 0)) << round(f * order) / order;
return ss.str();
}

c++11 has std::round so you won't need my version of with a new compiler.

The trick I'm exploiting here is to get the precision you want by taking the base 10 log to count the number of digits before the decimal and subtracting this from the precision you want.

It satisfies @Mats Petersson's requirement too, so will work in all cases.

The bit I don't like is the initial check for zero (so the log function doesn't blow up). Suggestions for improvement / direct editing of this answer most welcome.

How do I format a floating point value so that it never uses exponent notation nor has trailing zeros?

Since there doesn't seem to be a proper way to do this with the stdlib, here's my wrapper.

  template <typename T>
struct FpFormat {
template <typename Stream>
static Stream& setfmt(Stream& str) {
return str;
}
template <typename String>
static String const& untrail(String const& str) {
return str;
}
};
template <typename T>
struct FpFormatFloats {
template <typename Stream>
static auto setfmt(Stream& str) -> decltype(str << std::fixed << std::setprecision(std::numeric_limits<T>::digits10)) {
return str << std::fixed << std::setprecision(std::numeric_limits<T>::digits10);
}
template <typename String>
static String untrail(String str) {
if (str.find('.') == String::npos)
return str;
return ([](String s){
return String(s.begin(),s.begin()+((s.back() == '.')?(s.size()-1):s.size()));
})(str.substr(0,(str+"0").find_last_not_of('0')+1));
}
};
template <> struct FpFormat<float> : FpFormatFloats<float> {};
template <> struct FpFormat<double> : FpFormatFloats<double> {};
template <> struct FpFormat<long double> : FpFormatFloats<long double> {};

template <typename T>
std::string toString(T x) {
std::stringstream str;
FpFormat<T>::setfmt(str) << x;
return FpFormat<T>::untrail(str.str());
}

Best way to display decimal without trailing zeroes

Do you have a maximum number of decimal places you'll ever need to display? (Your examples have a max of 5).

If so, I would think that formatting with "0.#####" would do what you want.

    static void Main(string[] args)
{
var dList = new decimal[] { 20, 20.00m, 20.5m, 20.5000m, 20.125m, 20.12500m, 0.000m };

foreach (var d in dList)
Console.WriteLine(d.ToString("0.#####"));
}

Remove trailing zero in C++

This is one thing that IMHO is overly complicated in C++. Anyway, you need to specify the desired format by setting properties on the output stream. For convenience a number of manipulators are defined.

In this case, you need to set fixed representation and set precision to 2 to obtain the rounding to 2 decimals after the point using the corresponding manipulators, see below (notice that setprecisioncauses rounding to the desired precision). The tricky part is then to remove trailing zeroes. As far as I know C++ does not support this out of the box, so you have to do some string manipulation.

To be able to do this, we will first "print" the value to a string and then manipulate that string before printing it:

#include <iostream>
#include <iomanip>

int main()
{
double value = 12.498;
// Print value to a string
std::stringstream ss;
ss << std::fixed << std::setprecision(2) << value;
std::string str = ss.str();
// Ensure that there is a decimal point somewhere (there should be)
if(str.find('.') != std::string::npos)
{
// Remove trailing zeroes
str = str.substr(0, str.find_last_not_of('0')+1);
// If the decimal point is now the last character, remove that as well
if(str.find('.') == str.size()-1)
{
str = str.substr(0, str.size()-1);
}
}
std::cout << str << std::endl;
}


Related Topics



Leave a reply



Submit