【问题标题】:Commonly available functions for accurate floating-point number printing用于精确浮点数打印的常用函数
【发布时间】:2021-03-26 18:04:22
【问题描述】:

弗洛里安·洛伊奇 (Florian Loitsch) 的杰作叫"Printing Floating-Point Numbers Quickly and Accurately with Integers"。在将任何类型的 double 打印到字符串中的情况下,这听起来非常有用。我想包括我在内的很多人直到最近才知道使用setprecisiton(n)printf("%0.nf") 设置固定精度,并且经常选择n 太大而导致浮点不精确或太小而切断有价值的数据。我想知道在 C++ 标准(包括即将推出的)、boost、libc 或易于访问和测试的地方是否有类似的东西?

【问题讨论】:

  • 阅读 PDF 之前:printf("%g", value)?
  • 使用默认精度 6,即您必须再次指定精度。
  • C 和 C++ 是不同的语言,但会产生不同的答案。我删除了 C 标记,因为问题特别提到了 C++ 标准,因此似乎针对 C++。
  • 正如论文所说,“正确的打印成为许多语言规范的一部分,而且所有主要的 C 库(因此所有依赖 printf 函数的程序)都采用了准确的算法和打印现在得到正确的结果。”

标签: c++ floating-point


【解决方案1】:

我认为标准 C++ 库中没有任何真正好的解决方案。

我使用尝试几种不同格式的技巧,然后选择最短的文本表示并且文本表示在往返时具有完全相同的值。我用来保存在我的文本文件 (XML) 输出中。

这有时意味着选择精确的十六进制浮点表示。 (可能会破坏您的用例。)

这是代码,我添加了一个表格来生成一些示例,如果它们是最短的,则将它们标记为*,如果它们不往返,则使用 警告 标记.

#include <iomanip>
#include <ios>
#include <iostream>
#include <sstream>

using std::cout;
using std::defaultfloat;
using std::fixed;
using std::hexfloat;
using std::istringstream;
using std::left;
using std::scientific;
using std::setw;
using std::string;
using std::stringstream;

enum class cap { title, middle, end };

static void print(const char* text, double num, cap c) {
    stringstream ss;
    ss << fixed << num;
    auto s = ss.str();
    double fn = stod(s);
    size_t flen = s.length();
    string falert = "";
    if (fn != num) { falert = "/!\\"; flen += 100; }

    ss.str("");
    ss.clear();
    ss << scientific << num;
    s = ss.str();
    double sn = stod(s);
    size_t slen = s.length();
    string salert = "";
    if (sn != num) { salert = "/!\\"; slen += 100; }

    ss.str("");
    ss.clear();
    ss << hexfloat << num;
    s = ss.str();
    double hn = stod(s);
    size_t hlen = s.length();
    string halert = "";
    if (hn != num) { halert = "/!\\"; hlen += 100; }

    ss.str("");
    ss.clear();
    ss << defaultfloat << num;
    s = ss.str();
    double dn = stod(s);
    size_t dlen = s.length();
    string dalert = "";
    if (dn != num) { dalert = "/!\\"; dlen += 100; }

    char gbuf[256];
    sprintf(gbuf, "%g", num);
    s = gbuf;
    double gn = stod(s);
    size_t glen = s.length();
    string galert = "";
    if (gn != num) { galert = "/!\\"; glen += 100; }

    if (flen <= slen && flen <= hlen && flen <= dlen) falert += "*";
    if (slen <= flen && slen <= hlen && slen <= dlen) salert += "*";
    if (hlen <= flen && hlen <= slen && hlen <= dlen) halert += "*";
    if (dlen <= flen && dlen <= hlen && dlen <= slen) dalert += "*";
    if (glen <= dlen && glen <= flen && glen <= hlen && glen <= slen) galert += "*";

    if (c == cap::title) cout <<
    "┌──────────┬────────────┬──────────────────────────┐\n"
    "│  number  │   iomanip  │      representation      │\n"
    "├──────────┼────────────┼──────────────────────────┤\n"
    ;
    cout << left
    << "│ " << setw(8) << text << " │ fixed      │ " << setw(24) << fixed        << num << " │" << falert << "\n"
    << "│ " << setw(8) << text << " │ scientific │ " << setw(24) << scientific   << num << " │" << salert << "\n"
    << "│ " << setw(8) << text << " │ hexfloat   │ " << setw(24) << hexfloat     << num << " │" << halert << "\n"
    << "│ " << setw(8) << text << " │ default    │ " << setw(24) << defaultfloat << num << " │" << dalert << "\n"
    << "│ " << setw(8) << text << " │ %g         │ " << setw(24) << gbuf                << " │" << galert << "\n"
    ;
    cout << (c != cap::end ?
    "├──────────┼────────────┼──────────────────────────┤\n" :
    "└──────────┴────────────┴──────────────────────────┘\n" );
}

static void table() {
    print("0.0", 0.0, cap::title);
    print("0.01", 0.01, cap::middle);
    print("0.00001", 0.00001, cap::middle);
    print("1e99"   , 1.e+99, cap::middle);
    print("0.1"    , 0.1, cap::middle);
    print("0.2"    , 0.2, cap::middle);
    print("0.3"    , 0.3, cap::middle);
    print("0.4"    , 0.4, cap::middle);
    print("0.5"    , 0.5, cap::middle);
    print("0.6"    , 0.6, cap::middle);
    print("0.7"    , 0.7, cap::middle);
    print("0.8"    , 0.8, cap::middle);
    print("0.9"    , 0.9, cap::middle);
    print("NTSC"   , 30.0/1001.0, cap::middle);
    print("1/3"    , 1.0/3.0, cap::end);
}

int main() {
    table();
}

【讨论】:

    【解决方案2】:

    精确浮点数打印的常用函数

    最初标记为 [C] 和 [C++] 的帖子

     // Hexadecimal/decimal exponential output.
     printf("%a\n", value);  
    
     // Decimal exponential output with sufficient precision to distinguish against other double
     printf("%.*e\n", DBL_DECIMAL_DIG - 1, double_value);  
    
     // Decimal sometimes exponential output with sufficient precision to distinguish against other double
     printf("%.*g\n", DBL_DECIMAL_DIG, double_value);  
    

    所有有限浮点值都是精确的。很少有人需要将其完全视为小数。这需要more code

    【讨论】:

    • OP 可能需要生成唯一标识要转换的特定数字的原始值所需的最少位数的算法。 C++ 库例程不这样做,即使它们正确舍入(标准不需要,除非它已升级)。
    • @EricPostpischil 使用非 OP 编辑​​语言标签,这个 C 答案可能仍然为 OP 提供一些启示。至于OP真正想要的是什么,如果不是标题的“用于精确浮点数打印的常用功能”,也可能会演变。
    • @chux 谢谢。但这行不通,它不能准确地代表数字。例如,100.74 使用这种方法会以单精度错误表示。
    • @Yuki 这当然是内部表示的问题。大多数十进制分数只能由二进制分数近似,这就是有限精度的 IEEE-754 二进制浮点数。这似乎是“浮点数学被破坏了吗?”的副本
    • @njuffa 我不认为数学是错误的。我知道浮点如何在内部工作的一些基础知识。您可以尝试阅读报纸的乞讨,以了解我在说什么。
    猜你喜欢
    • 2012-07-14
    • 2011-03-14
    • 1970-01-01
    • 2021-05-15
    • 1970-01-01
    • 2012-06-09
    • 2021-07-25
    • 1970-01-01
    • 2015-02-02
    相关资源
    最近更新 更多