【问题标题】:Precise floating-point<->string conversion精确的浮点<->字符串转换
【发布时间】:2009-08-21 10:45:28
【问题描述】:

我正在寻找一个库函数来在 C++ 中将浮点数转换为字符串,然后再转换回来。我想要的属性是 str2num(num2str(x)) == x 和 num2str(str2num(x)) == x (尽可能)。一般属性是 num2str 应该表示最简单的有理数,当四舍五入到最接近的可表示浮点数时,它会返回原始数字。

到目前为止,我已经尝试过 boost::lexical_cast:

double d = 1.34;
string_t s = boost::lexical_cast<string_t>(d);
printf("%s\n", s.c_str());
// outputs 1.3400000000000001

我已经尝试过 std::ostringstream,如果我执行 stream.precision(16),这似乎适用于大多数值。但是,在精度为 15 或 17 时,它会截断或给出难看的输出,例如 1.34。我不认为精度 16 可以保证具有我需要的任何特定属性,并且怀疑它会分解许多数字。

是否有具有这种转换的 C++ 库?或者这样的转换函数是否已经埋在标准库/boost的某个地方。

需要这些函数的原因是将浮点值保存到 CSV 文件中,然后正确读取它们。此外,我希望 CSV 文件尽可能包含简单的数字,以便人类使用。

我知道 Haskell 读取/显示函数已经具有我所追求的属性,BSD C 库也是如此。 stringdouble 转换的标准参考是 PLDI 1990 的一对论文:

  • 如何准确读取浮点数,Will Klinger
  • Guy Steele 等人如何准确打印浮点数

任何基于这些的 C++ 库/函数都是合适的。

编辑:我完全知道浮点数是十进制数的不精确表示,并且 1.34==1.3400000000000001。但是,正如上面引用的论文指出的那样,这不是选择显示为“1.3400000000000001”的借口

EDIT2:本文准确解释了我正在寻找的内容:http://drj11.wordpress.com/2007/07/03/python-poor-printing-of-floating-point/

【问题讨论】:

  • 当我搜索一个时,我在 C 中找到了一个,而不是在 C++ 中。我这里没有链接。我记得好像是在 NAG 的 ftp 网站上,但我可能是错的。
  • C 库也同样适用 - 我现在只是在查看 NAG 文档。
  • 您可以研究 GMP 和 MPFR 以进行软件浮点仿真。但是对于 C++ 的 floatdouble 类型,您所要求的几乎是不可能的。

标签: c++


【解决方案1】:

我仍然无法找到提供必要代码的库,但我确实找到了一些有效的代码:

http://svn.python.org/view/python/branches/py3k/Python/dtoa.c?view=markup

通过提供相当少的定义,很容易抽象出 Python 集成。这段代码确实符合我概述的所有属性。

【讨论】:

    【解决方案2】:

    我认为结合标准库的 strtod() 可以满足您的需求:

    #include <stdio.h>
    #include <stdlib.h>
    
    int dtostr(char* buf, size_t size, double n)
    {
      int prec = 15;
      while(1)
      {
        int ret = snprintf(buf, size, "%.*g", prec, n);
        if(prec++ == 18 || n == strtod(buf, 0)) return ret;
      }
    }
    

    一个简单的演示,无需检查输入单词的尾随垃圾:

    int main(int argc, char** argv)
    {
      int i;
      for(i = 1; i < argc; i++)
      {
        char buf[32];
        dtostr(buf, sizeof(buf), strtod(argv[i], 0));
        printf("%s\n", buf);
      }
      return 0;
    }
    

    一些示例输入:

    % ./a.out 0.1 1234567890.1234567890 17 1e99 1.34 0.000001 0 -0 +INF NaN
    0.1
    1234567890.1234567
    17
    1e+99
    1.34
    1e-06
    0
    -0
    inf
    nan
    

    我想你的 C 库需要符合一些足够新的标准版本,以保证正确的舍入。

    我不确定我在prec 上选择了理想的界限,但我想它们一定很接近。也许他们可以更紧?同样,我认为buf 的 32 个字符总是足够的,但从来没有必要。显然,这一切都假设 64 位 IEEE 双打。可能值得用某种巧妙的预处理器指令检查该假设——sizeof(double) == 8 将是一个好的开始。

    指数有点乱,但在跳出循环后但在返回之前修复并不难,也许使用memmove()或类似的东西向左移动。我很确定最多可以保证有一个+ 和最多一个领先的0,而且我认为它们甚至不能同时出现prec &gt;= 10 左右。

    同样,如果您宁愿忽略带符号的零,就像 Javascript 所做的那样,您可以轻松地预先处理它,例如:

    if(n == 0) return snprintf(buf, size, "0");
    

    我很想看到与您在 Python 代码库中挖掘的 3000 行怪物的详细比较。大概简短的版本更慢,或更不正确,还是什么?如果两者都不是,那将是令人失望的......

    【讨论】:

    • 我调查了一个基准套件。您的版本的 VS2008 上的结果同样好,但并不完全相同 - 例如,第一个算法更喜欢 87.21565540666982 而您的算法更喜欢 87.21565540666983,但两者都具有相同的位表示。你的算法也慢了 3%。但是考虑到 1000 行丑陋的 C 与你相当优雅的答案,你肯定会赢:)。
    【解决方案3】:

    需要这些函数的原因是将浮点值保存到 CSV 文件中,然后正确读取它们。此外,我希望 CSV 文件尽可能包含简单的数字,以便人类使用。

    你不能在转换 double → string → double 的同时让字符串可读。

    您需要在精确转换和人类可读字符串之间进行选择。这是max_digits10digits10的定义:

    这是num2strstr2num 的实现,具有两个不同的上下文from_double(转换双精度→字符串→双精度)和from_string(转换字符串→双精度→字符串):

    #include <iostream>
    #include <limits>
    #include <iomanip>
    #include <sstream>
    
    namespace from_double
    {
      std::string num2str(double d)
      {
        std::stringstream ss;
        ss << std::setprecision(std::numeric_limits<double>::max_digits10) << d;
        return ss.str();
      }
    
      double str2num(const std::string& s)
      {
        double d;
        std::stringstream ss(s);
        ss >> std::setprecision(std::numeric_limits<double>::max_digits10) >> d;
        return d;
      }
    }
    
    namespace from_string
    {
      std::string num2str(double d)
      {
        std::stringstream ss;
        ss << std::setprecision(std::numeric_limits<double>::digits10) << d;
        return ss.str();
      }
    
      double str2num(const std::string& s)
      {
        double d;
        std::stringstream ss(s);
        ss >> std::setprecision(std::numeric_limits<double>::digits10) >> d;
        return d;
      }
    }
    
    int main()
    {
      double d = 1.34;
      if (from_double::str2num(from_double::num2str(d)) == d)
        std::cout << "Good for double -> string -> double" << std::endl;
      else
        std::cout << "Bad for double -> string -> double" << std::endl;
    
      std::string s = "1.34";
      if (from_string::num2str(from_string::str2num(s)) == s)
        std::cout << "Good for string -> double -> string" << std::endl;
      else
        std::cout << "Bad for string -> double -> string" << std::endl;
    
      return 0;
    }
    

    【讨论】:

      【解决方案4】:

      其实我想你会发现 1.34 是 1.3400000000000001。浮点数并不精确。你无法解决这个问题。例如 1.34f 是 1.34000000333786011。

      【讨论】:

      • 没错,那么为什么在给定 1.34f 时不显示较短的呢?这就是我所要求的:-)
      【解决方案5】:

      正如其他人所说。浮点数并不那么准确,它是它们如何存储值的人工制品。

      您真正需要的是十进制数字表示。 基本上这使用一个整数来存储数字,并且在小数点后具有特定的精度。

      快速谷歌得到了这个: http://www.codeproject.com/KB/mcpp/decimalclass.aspx

      【讨论】:

      • 一般来说十进制数当然是首选,但在这种情况下,由于系统上的其他限制,我真的想使用浮点数。
      • 实际上,我更喜欢有理数:haskell.org/ghc/docs/latest/html/libraries/base/Data-Ratio.html - 它们具有更大的代表性。
      • 是的。我已经将其视为表示任意精度算术的一种方式(但这不是您所要求的)。您想要(ed)一种表示任意精度浮点值的方法(相似但不相同)。那么,哪些未提及的约束会迫使您使用流动点值。
      • 约束很常见:现有的大量代码库、高性能问题、架构限制等。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-12
      • 2013-05-07
      • 1970-01-01
      • 1970-01-01
      • 2011-11-25
      相关资源
      最近更新 更多