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

风流意气都作罢 提交于 2019-12-07 00:33:17

问题


According to ios_base manipulators, I basically have the choice between defaultfloat and fixed when formatting floating point numbers without exponent notation (with decimal numbers).

However, I want to choose the maximum precision which would produce a lot trailing zeros for fixed for many numbers (e.g. 1.) but avoid ever using the exponent notation. If set to defaultfloat, it will look right most of the time, unless the value is really really small, yet not 0.. In that case, the default representation switches to scientific notation on its own, which breaks the receiver of the formatted output (since it has no clue what 2.22045e-16 means.

So, how can I have my pie and eat it, too? That is, non-exponent notation without unnecessary trailing zeroes.


Note: I did not test the effect of the defaultfloat flag, since my gcc doesn't seem to implement that flag (yet), but I assume it is the default setting which applies without using any flag. I did check the fixed flag, which does behave as expected.


回答1:


A simple method would be something like this:

std::string float2string(double f)
{
    std::stringstream ss;

    ss << std::fixed << std::setprecision(122) << f;   // 122 is LARGE, but you may find that really tiny or really large numbers still don't work out... 

    std::string s = ss.str();

    std::string::size_type len = s.length();

    int zeros = 0;
    while(len > 1 && s[--len] == '0')
        zeros++;
    if (s[len] == '.')  // remove final '.' if number ends with '.'
        zeros++;
    s.resize(s.length()-zeros);


    return s;
}

I have given it some testing. The biggest problem is that it gives a huge number of decimals for some numbers, and things like 0.05 comes out as 0.05000000000000000277555756156289135105907917022705078125 and 0.7 becomes: 0.05000000000000000277555756156289135105907917022705078125

That's because it's not an "exact" number in binary form.

I think the solution is to calculate roughly how many digits you want in the result, by taking the number of integer digits (or floor(log(f)/log(10)+1)) and then subtracting that number from a constant, such as 17, which is the number of digits you can expect from a double. Then remove surplus zeros.

I'll leave that for the moment, as I've got some other stuff to do that I should have started on a little while ago... ;)




回答2:


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());
  }



回答3:


Sadly it seems like there is no code to do this in either iostream or printf(). Any by "this" I mean:

  1. Print in fixed format.
  2. Print the minimum number of digits so that the encoding is lossless (i.e. the closest valid floating point number is the one that you printed).

You can't just print with a very large precision and strip trailing zeros because there might be non-zero digits that are unnecessary.

I believe the only good way to really do this would be to use dedicated code like Ryu.

Another way would be to print the next and previous numbers with very large precision and take the digits after the decimal point that are the same (plus one). It's going to be ugly though.




回答4:


There's no built-in formatting that will do this for you. You can either produce your own formatter from scratch or you could write one that post-processes the string to remove unnecessary trailing zeros.

For example you could do it using regex substitution:

#include <iomanip>
#include <iostream>
#include <sstream>
#include <regex>

// not thoroughly tested
std::string format(double d, int precision = 100) {
   std::stringstream ss;
   ss << std::fixed << std::setprecision(precision);
   ss << d;

   return std::regex_replace(
       std::regex_replace(ss.str(), std::regex(R"((-?\d*\.\d*?)0*$)"), "$1"),
       std::regex(R"(\.$)"), "");
}

int main() {
   std::cout << format(-1) << '\n';
   std::cout << format(1e20) << '\n';
   std::cout << format(1e-20) << '\n';
   std::cout << format(2.22045e-16) << '\n';
}

Though using a regex is probably not a particularly efficient solution.



来源:https://stackoverflow.com/questions/14965902/how-do-i-format-a-floating-point-value-so-that-it-never-uses-exponent-notation-n

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!