精确的浮点< - >字符串转换

时间:2009-08-21 10:45:29

标签: c++

我正在寻找一个库函数来将浮点数转换为字符串,然后再用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库也是如此。字符串&lt; - &gt; double转换的标准参考是来自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/

5 个答案:

答案 0 :(得分:4)

我仍然无法找到提供必要代码的库,但我找到了一些可行的代码:

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

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

答案 1 :(得分:3)

我认为这与你想要的结合标准库的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 >= 10或如此。

同样,如果你宁愿忽略已签名的零,就像Javascript那样,你可以很容易地预先处理它,例如:

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

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

答案 2 :(得分:1)

  

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

你不能将转换双倍→字符串→双倍,并且同时具有人类可读的字符串。

您需要在精确转换和人类可读字符串之间进行选择。这是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;
}

答案 3 :(得分:0)

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

答案 4 :(得分:0)

正如其他人所说。浮点数对于它们存储值的方式而言并不准确。

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

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