【问题标题】:istringstream rounds long numbers - how can I prevent that ?istringstream 对长数字进行四舍五入 - 我该如何防止这种情况?
【发布时间】:2012-12-04 09:31:34
【问题描述】:

给定以下代码:

istringstream i("2.11099999999999999999");

double d;
if (!(i >> d)) {d = 0;}

cout << d << endl;

输出为 2.111

我希望能够处理长数字、浮点数(包括浮点数),但是当我将 istringstream 转换为 double 时,我得到一个四舍五入的数字。

我怎样才能防止这种情况发生?如何保持给定的输入原样?

问候

【问题讨论】:

标签: c++ string stringstream istringstream


【解决方案1】:

在这种情况下,您无法阻止它。 double 不能精确地表示值 2.11099999999999999999,并且它可以表示的值都不能将 2.11099999999999999999 与 2.111 区分开来。

http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html 应该告诉你你需要知道的,甚至更多。

如果您使用不同的示例,其中double 可以表示区分舍入值和未舍入值的值,那么您可以这样做:

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

int main() {
    std::istringstream iss("2.1109999");
    double d;
    iss >> d;
    std::cout << d << "\n";
    std::cout << std::setprecision(10) << d << "\n";
}

输出:

2.111
2.1109999

不过,您应该知道,d 中存储的值并不完全是 2.1109999

std::cout << std::setprecision(20) << d << "\n";

输出(在我的机器上,你的可能不同,因为某些运行时库根本不打印到 20 s.f):

2.1109998999999999292

这是因为double 将值存储在二进制 中,而不是十进制。所以它只能表示终止二进制分数。 2.1109999 不是终止二进制分数,原因与三分之一不是终止十进制分数的原因基本相同。

因此,有两种方法可以使给定的输入保持原样(即精确地表示该数字):

  1. 不要转换为double,将其保留为字符串。
  2. 不要转换为double,而是查找或编写一个表示十进制分数和/或有理数的库。例如,GMP 库有mpq_t 或有Boost.Rational

【讨论】:

    【解决方案2】:

    在数学空间中有无数个数字,double 是有限的,即它适合 64 位,因此只有这些值的一个子集被精确表示。实际上 2.111 也没有精确表示,因为虽然数字是用小数点打印的,但在下面它们确实使用二进制,即尾数(52 位精度)和指数加号位。

    由于尾​​数的精度约为 52 位,因此 2 的 52 次方为 4,503,599,627,370,496

    如您所见,这个数字在最初的 4 之后有 15 位,因此您得到 15 位小数精度。

    对于大多数用途,这种精度已经足够了。对于那些需要更高精确度的场合,或者需要大数量和小改动的特殊情况,您需要采用专业技术。有一些库会为您提供这些。

    【讨论】:

      【解决方案3】:

      如前所述,您无法获得那种精确度。 您可以随时增加显示的位数,如下所述:Increase precision

      【讨论】:

      • 链接的答案并没有告诉您如何提高精度,它只是告诉您如何增加显示的位数 - 差异非常重要。
      • 我错过了短语中一个非常重要的词!对不起,我会编辑!
      【解决方案4】:

      您需要将数字存储在双精度以外的其他形式,这是一种二进制表示。

      四处寻找一个将数字存储为十进制表示的库,我知道 boost 有一个 http://svn.boost.org/svn/boost/sandbox/big_number/libs/multiprecision/doc/html/index.html,但还有很多其他的。

      如果这似乎是一个重量级的解决方案,请尝试一次读取 istringstream 一个字符,如果是数字,将其从 char 转换为 int,然后存储在数组中。

      未经测试的代码

      string s = "2.11099999999999999999";
      char num[s.size()];
      for (int i = 0; i < s.size(); ++i) {
        if (isdigit(s[i])
          num[i] = s[i] - '0';
        else
          num[i] = s[i];
      }
      

      【讨论】:

        【解决方案5】:

        这里有两个单独的转换:从文本到双精度的转换 (iss &gt;&gt; d),以及从双精度到文本的转换 (std::cout &lt;&lt; d)。第一个将输入文本的最佳近似值存储到您的double 变量中。第二个使用默认精度;它将值四舍五入以适应该精度,并抑制尾随零。这就是您看到2.111 的原因。如果要查看更多位数,请使用 setprecision 增加输出中的位数。正如其他人所说,超过 15 位(输入或输出)的任何内容都是无稽之谈。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-07-02
          • 2010-11-29
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多