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

Posted

技术标签:

【中文标题】如何格式化浮点值,使其从不使用指数表示法,也没有尾随零?【英文标题】:How do I format a floating point value so that it never uses exponent notation nor has trailing zeros? 【发布时间】:2013-02-04 15:08:28 【问题描述】:

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

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

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


注意:我没有测试defaultfloat 标志的效果,因为我的 gcc 似乎还没有实现该标志,但我认为它是默认设置,没有使用任何标志。我确实检查了fixed 标志,它的行为确实符合预期。

【问题讨论】:

当输出为 2.22045e-16 时,您究竟想打印什么? 00.00000000000000000222045 [不确定我得到了正确数量的零,但假装它是......] @MatsPetersson:第二个。 如果值为 16.00001210121019,你想要的正是那个,但如果值为 16.5,你想要那个,对吗?我认为您最好编写自己的“浮点到字符串”。这并不难。 @MatsPetersson:是的,你是对的。但是,关于编写我自己的“从浮点数到字符串”……我讨厌重新发明***。特别是在这里,因为我认为这应该是浮点格式的默认情况。 好吧,正如你所注意到的。我正在寻找答案,但可能需要几分钟,因为我可能至少会通过它运行一些简单的东西...... 【参考方案1】:

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

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(log(f)/log(10)+1))然后从一个常数,例如 17,这是您可以从 double 中获得的位数。然后删除多余的零。

我暂时先不说这个,因为我还有一些其他的事情要做,我应该在不久前就开始做……;)

【讨论】:

谢谢。 +1 努力,但my version 似乎可以正确处理这些数字。 可悲的是,这硬编码.(见coliru.stacked-crooked.com/a/b72ae760073d8832) 如果您不想硬编码最大指数std::setprecision(std::numeric_limits&lt;FloatType&gt;::max_exponent) 应该是numeric_limits&lt;T&gt;::max_digits10,而不是max_exponent。请参阅示例here。 其实这两个都不对,因为setprecision()想要小数点后的位数,max_digits10是有效位数。 max_exponent 是错误的。正确的数字应该是 max_exponent10 + max_digits10(可能是 +1 或 2),超过 300,所以这不是一个好的通用解决方案......【参考方案2】:

由于似乎没有正确的方法来使用 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());
  

【讨论】:

可悲的是,这硬编码.(见coliru.stacked-crooked.com/a/b72ae760073d8832) 我意识到你接受了你自己的答案,所以这段代码对你有用,但它实际上并没有满足“从不使用指数表示法”的要求(尝试非常大或非常小的数字)。 Mats 的回答更接近于击中钉子。我只是写这个评论来通知其他需要无指数功能的人。 我认为你应该使用max_digits10 而不是digits10【参考方案3】:

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

    以固定格式打印。 打印最小位数,以便编码无损(即最接近的有效浮点数是您打印的那个)。

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

我相信真正做到这一点的唯一好方法是使用像 Ryu 这样的专用代码。

另一种方法是以非常高的精度打印下一个和上一个数字,并取小数点后相同的数字(加一个)。不过会很丑。

【讨论】:

【参考方案4】:

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

例如,您可以使用正则表达式替换:

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

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

【讨论】:

可悲的是,这硬编码.(见coliru.stacked-crooked.com/a/b72ae760073d8832)

以上是关于如何格式化浮点值,使其从不使用指数表示法,也没有尾随零?的主要内容,如果未能解决你的问题,请参考以下文章

Boost C++如何输出没有指数字符的浮点值的所有数字?

浮点型是啥意思

IEEE 754 中的指数

C51浮点数显示浮点数表示方法

java float浮点型数据存储,丢失精度问题

浮点数的十六进制表示