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
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.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 setprecision
causes 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
Simple 3X3 Matrix Inverse Code (C++)
Opencv - Dll Missing, But It's Not
Search 25 000 Words Within a Text
Best Practices for Use of C++ Header Files
What Is the Use of Volatile Keyword
How to Handle or Avoid a Stack Overflow in C++
How to Build Libcxx and Libcxxabi by Clang on Centos 7
Is There a Reason Why Not to Use Link-Time Optimization (Lto)
Print Out the Values of a (Mat) Matrix in Opencv C++
How to Access a Global Variable Within a Local Scope
How to Call a Template Member Function
How to Get Python Exception Text
How to Get a List of Files in a Folder in Which the Files Are Sorted with Modified Date Time
Fast Implementation of Trigonometric Functions for C++
Get Current Working Directory in a Qt Application
Implementing Future::Then() Equivalent for Asynchronous Execution in C++11
C++ Specialization of Template Function Inside Template Class