双精度和字符串流格式化

19
double val = 0.1;
std::stringstream ss;
ss << val;
std::string strVal= ss.str();
在 Visual Studio 的调试器中,val 的值为 0.10000000000000001(因为 0.1 无法精确表示)。当使用 stringstream 进行转换时,strVal 等于 "0.1"。然而,使用 boost::lexical_cast 时,得到的 strVal"0.10000000000000001"
另外一个例子如下: double val = 12.12305000012; 在 Visual Studio 中,val 显示为 12.123050000119999,并且使用 stringstream 和默认精度(6)时,它变成了 12.1231。我不太理解为什么它不是 12.12305(...)。
是否有默认精度,或者 stringstream 是否具有将不能精确表示的 double 值转换的特定算法?
谢谢。
4个回答

20

你可以按照以下方式更改 stringstream 的浮点精度:

double num = 2.25149;
std::stringstream ss(stringstream::in | stringstream::out);
ss << std::setprecision(5) << num << endl;
ss << std::setprecision(4) << num << endl;

输出:

2.2515
2.251

请注意,数字在适当的情况下也会四舍五入。


2
在 Visual Studio C++ 中测试时,我只能通过首先调用 ss.setf(ios::fixed); 来使其正常工作。如果我不这样做,那么精度就不会改变。 - Sam Goldberg
16
使用std::setprecision(std::numeric_limits<double>::digits10)可以获得完整的双精度浮点数精度。 - kuga
digits10 在 Visual Studio 2015 上无法正常工作。请尝试使用 18014398509481980.018014398509481982.0 进行测试,至少需要 17 位才能获得完整的精度。请参考 https://dev59.com/WG025IYBdhLWcg3wQDlb 并在 https://onlinegdb.com/Sk6yXmeY7 进行尝试。 - Kevin Smyth
1
cppreference在此指出,最大精度可通过以下方式实现:std::setprecision(std::numeric_limits<long double>::digits10 + 1) - Max Levy
自 C++11 起,您还可以使用 std::numeric_limits<T>::max_digits10。有关 std::numeric_limits<T>::max_digits10std::numeric_limits<T>::digits10 之间的区别,请参见 https://dev59.com/z2Eh5IYBdhLWcg3wZyvG#22458961。 - Catree

18

如果有人遇到 "error: ‘setprecision’ is not a member of ‘std’",那么你必须要 #include <iomanip>,不然就无法使用 setprecision(17)


8
这应该是一个评论,或对现有答案的编辑。 - Mark Ingram
@MarkIngram 我非常抱歉,这不会再发生。 - YuZ

5
有两个问题需要考虑。第一个是精度参数,默认为6(但您可以将其设置为任何您喜欢的值)。第二个问题是此参数的含义,这取决于您使用的格式选项:如果您使用固定或科学格式,则它表示小数点后的位数(这又会对两种格式中通常所指的精度产生不同的影响);但是,如果您使用默认精度(ss.setf(std::ios_base::fmtflags(), std::ios_base::formatfield)),则它表示输出中的数字数量,而不管实际上是否使用了科学或固定记数法进行格式化。这就解释了为什么您的显示结果是12.1231;因为您同时使用了默认精度和默认格式。
您可能想尝试使用不同的值(也许是不同的精度)来测试以下内容:
std::cout.setf( std::ios_base::fmtflags(), std::ios_base::floatfield );
std::cout << "default:    " << value[i] << std::endl;
std::cout.setf( std::ios_base::fixed, std::ios_base::floatfield );
std::cout << "fixed:      " << value[i] << std::endl;
std::cout.setf( std::ios_base::scientific, std::ios_base::floatfield );
std::cout << "scientific: " << value[i] << std::endl;

看到实际的输出可能会比任何详细的描述更清晰:

default:    0.1
fixed:      0.100000
scientific: 1.000000e-01

4
问题出现在流插入操作ss << 0.1;时,而不是转换为字符串时。如果您需要非默认精度,您需要在插入double之前指定其精度:
ss << std::setprecision(17) << val;

在我的电脑上,如果我只使用setprecision(16),我仍然会得到"0.1"而不是"0.10000000000000001"。我需要精确到17位才能看到最后的1。
补充说明: 当值为1.0/3.0时,更好的示例出现了。使用默认精度,您会得到"0.333333"的字符串表示形式。这不是double精度1/3的字符串等价物。使用setprecision(16)使字符串变为"0.3333333333333333";精度为17产生"0.33333333333333331"

我已经尝试了以下真实数值12.12305000012,在Visual Studio计算机中它显示为12.123050000119999,并且使用stringstream和默认精度(6不是吗?)变成了12.1231。我真的不明白为什么它不是12.12305(..something)。 - Guillaume Paris
1
保留六位小数,12.12305000012 12.1231。这种四舍五入是在插入流时发生的。您可以通过 setprecision 控制此舍入行为 - 或者您可以简单地使用 Boost::lexical_castBoost::lexical_cast 也使用字符串流,但它在插入时指定精度以尝试确保不会丢失任何内容。对于浮点类型,lexical_cast 使用比最大精度多一点以最小化损失。 - David Hammen
好的,因为精度为6意味着使用的总数字数量,而不是小数位数。 - Guillaume Paris
2
是的。setprecision 的作用取决于流的 floatfield 设置。它可以是 fixedscientific 或 (none)。对于 fixedsetprecision(<p>) 的行为类似于 C printf 函数族中的 "%.<p>f" 格式说明符;对于 scientific,则为 "%.<p>e";对于 (none),则为 "%.<p>g" - David Hammen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接