如何格式化浮点值,使其从不使用指数表示法,也不具有尾随零?

时间:2013-02-19 19:36:41

标签: c++ floating-point string-formatting iostream

根据ios_base manipulators,在格式化没有指数表示法的浮点数(带十进制数字)时,我基本上可以在defaultfloatfixed之间进行选择。

但是,我想为fixed选择maximum precision1.生成很多尾随零的许多数字(例如defaultfloat),但要避免使用指数表示法。如果设置为0.,它将在大多数中显示正确的时间,除非该值真的很小,但不是2.22045e-16。在这种情况下,默认表示会自行切换到科学记数法,这会打破格式化输出的接收者(因为它不知道defaultfloat的含义。

那么,我怎么能吃馅饼呢?也就是说,非指数表示法没有不必要的尾随零


注意:我没有测试fixed标志的效果,因为我的gcc似乎没有实现那个标志(但是),但我认为它是默认设置适用于不使用任何标志。我确实检查了{{1}}标志,它的行为符合预期。

4 个答案:

答案 0 :(得分:3)

一个简单的方法是这样的:

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

我已经给它一些测试。最大的问题是,它为某些数字提供了大量的小数,而像0.05这样的内容则显示为0.05000000000000000277555756156289135105907917022705078125 和0.7成为: 0.05000000000000000277555756156289135105907917022705078125

那是因为它不是二进制形式的“精确”数字。

我认为解决方案是通过取整数位数(或floor(f(f)/ log(10)+1))然后从中减去该数字来大致计算结果中您想要的位数。一个常数,例如17,这是你可以从双倍数中得到的数字位数。然后删除多余的零。

我暂时不会这样做,因为我还有其他一些事要做,我应该在不久前开始......;)

答案 1 :(得分:1)

因为使用stdlib似乎不是一个正确的方法,所以这就是我的包装器。

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

答案 2 :(得分:0)

可悲的是,iostreamprintf()中似乎没有代码可以执行此操作。任何“这个”的意思是:

  1. 以固定格式打印。
  2. 打印数字的 minimum ,这样编码是无损的(即,最接近的有效浮点数是您打印的数字)。

不能仅以非常高的精度打印并去除尾随零,因为可能会有不必要的非零数字。

我相信真正做到这一点的唯一好方法是使用专用代码,例如Ryu

另一种方法是非常精确地打印下一个和前一个数字,并取小数点后相同的数字(加一)。这将是丑陋的。

答案 3 :(得分:-1)

没有为您执行此操作的内置格式。您可以从头开始生成自己的格式化程序,也可以编写一个对字符串进行后处理以删除不必要的尾随零的格式化程序。

例如,您可以使用正则表达式替换来执行此操作:

#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';
}

虽然使用正则表达式可能不是一个特别有效的解决方案。