如何使用cout输出完整精度的浮点数?

446

在我的之前的问题中,我使用cout打印了一个double,但结果被四舍五入了,而我并不希望它这样。如何让cout以完整精度打印一个double


2
很遗憾,下面的大多数答案都是不正确的。我建议查看 https://dev59.com/dHjZa4cB1Zd3GeqPd25r 代替。 - vitaut
2
请注意,实际上并不存在所谓的“完全”精度。 - Mooing Duck
2
@AlexisWilke 我认为“它可能会以微妙的方式随机地与您的编译器不兼容,而且没有‘好’的解决方法”可以被归类为“非常糟糕”。无论如何,仅仅发布一个“在我的机器上运行良好”的片段,却不暗示一下发生了一些奇怪的事情是次优的。 - Niko O
4
FWIW:这个问题在博客文章中引起了一些关注: https://www.zverovich.net/2023/06/04/printing-double.html - kebs
4
顺便说一句:这个问题在一篇博客文章中引起了一些关注: https://www.zverovich.net/2023/06/04/printing-double.html - undefined
显示剩余4条评论
17个回答

492

您可以直接在std::cout上设置精度,并使用std::fixed格式说明符。

double d = 3.14159265358979;
cout.precision(17);
cout << "Pi: " << fixed << d << endl;

您可以使用#include <limits>来获取浮点数或双精度浮点数的最大精度。

#include <limits>

typedef std::numeric_limits< double > dbl;

double d = 3.14159265358979;
cout.precision(dbl::max_digits10);
cout << "Pi: " << d << endl;

52
为什么你明确建议使用fixed?对于double h = 6.62606957e-34;fixed输出为0.000000000000000,而scientific则输出为6.626069570000000e-34 - Arthur
39
精度需要为17(或std::numeric_limits<double>::digits10 + 2),因为将十进制转换回二进制表示时需要额外的两位数字,以确保将值四舍五入为相同的原始值。以下是一篇详细介绍的论文:http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html - Mike Fisher
10
这真的是正确的答案吗?当我手动使用一个很大的数字时,我可以打印出约51位的e的近似值,但使用cout.precision(numeric_limits<double>::digits10 + 2);只能得到16位... - Assimilater
7
在MikeFisher引用的论文中,如果你正在寻找提到17位数字的地方,它在第15个定理下。 - Emile Cormier
20
@MikeFisher 您是正确的,C++11引入了max_digits10来表示相同的内容。已修正答案以反映此更改。 - legends2k
显示剩余9条评论

99
在C++20中,您可以使用std::format来做到这一点。
std::cout << std::format("{}", std::numbers::pi_v<double>);

输出(假设IEEE 754 double):
3.141592653589793

默认的浮点数格式是最短的十进制表示,并且具有往返保证。与 setprecision I/O 操纵符相比,这种方法的优点是它不会打印不必要的数字,并且不受全局状态的影响(有关更多详细信息,请参阅 此博客文章)。
同时,您可以使用 {fmt}库 ,基于它的 std::format。{fmt} 还提供了使其更加简单和高效的 print 函数(godbolt)。
fmt::print("{}", M_PI);

这个问题实际上没有明确定义“完全精度”的含义。通常情况下,它被理解为足够精确以进行十进制的往返,但也有另一种可能但不太可能的解释,即(有效)十进制数字的最大数量。对于IEEE 754双精度浮点数,后者是767位数字。
免责声明:我是{fmt}和C++20 std::format的作者。

5
{fmt} 可与 gcc 4.4 及更高版本一起使用。 - vitaut
5
{fmt} 可与 gcc 4.4 及更高版本一起使用。 - undefined
7
看到你的超赞博客文章了。绿色勾勾已转账 <3 - undefined
6
看到你的超赞博客文章了。绿色勾号已转账 <3 - Restore the Data Dumps
2
@PhilippLudwig 这将由库来实现,而不是编译器。 - Keith Thompson
显示剩余9条评论

88

使用std::setprecision

#include <iomanip>
std::cout << std::setprecision (15) << 3.14159265358979 << std::endl;

2
有没有一种MAX_PRECISION宏、枚举或其他东西,我可以传递给std::setPrecision函数? - Jason Punyon
3
对于双精度数,使用std::setprecision(15)(可以接受16)进行精度控制,log_10(2**53)约为15.9。 - user7116
20
std::setprecision(std::numeric_limits<double>::digits10) - Éric Malenfant
9
针对双精度浮点数应使用std::setprecision(17),请查看@Bill The Lizard的回答评论。 - Alec Jacobson
16
要使用std::setprecision函数,需要包含头文件<iomanip>。 - user2262504
1
这个答案是误导性的,因为std::setprecision并不像人们想象的那样工作;例如,std::cout << std::setprecision(6) << 39927.887 << "\n";只会打印出39927.9。你真的应该提到这个参数指定了所有数字的数量,而不仅仅是小数点右边的数字。 - Philipp Ludwig

27

我会使用以下代码:

std::cout << std::setprecision (std::numeric_limits<double>::digits10 + 1)
          << 3.14159265358979
          << std::endl;
基本上,limits包含了所有内置类型的特性。 浮点数(float/double/long double)之一的特性是 digits10 属性。该属性定义了一个十进制浮点数的精度(我忘记了确切的术语)。 有关其他属性的详细信息,请参见:http://www.cplusplus.com/reference/std/limits/numeric_limits.html

14
使用std::setprecision()函数需要包含这个头文件:#include <iomanip> - Martin Berger
应该使用std::numeric_limits<double>而不是numberic_limits<double> - niklasfi
2
为什么要在 std::numeric_limits<double>::digits10 上加 1 - Alessandro Jacopson
6
你可以使用C++11的max_digits10代替。请参见此链接 - legends2k
4
应该使用“max_digits10”而不是任意的“digits10+2”。否则,在“float”、“long double”和“boost::multiprecision::float128”等情况下,这将失败,因为在这些情况下,你需要使用“+3”而不是“+2”。 - Ruslan
显示剩余3条评论

24

如何使用cout输出完整精度的double值?

使用 hexfloat
使用 scientific 并设置精度

std::cout.precision(std::numeric_limits<double>::max_digits10 - 1);
std::cout << std::scientific <<  1.0/7.0 << '\n';

// C++11 Typical output
1.4285714285714285e-01

很多答案只涉及1)进制 2)固定/科学格式或3)精度中的一个。 太多强调精度的答案未提供所需的正确值。 因此,这个回答是对一个老问题的回答。

  1. 什么进制?

double肯定是用基数2编码的。 在C++11中,一种直接的方法是使用std::hexfloat进行打印。
如果可以接受非十进制输出,则我们已经完成了。

std::cout << "hexfloat: " << std::hexfloat << exp (-100) << '\n';
std::cout << "hexfloat: " << std::hexfloat << exp (+100) << '\n';
// output
hexfloat: 0x1.a8c1f14e2af5dp-145
hexfloat: 0x1.3494a9b171bf5p+144

  1. 否则:使用 fixed 还是 scientific

double 是一种浮点类型,而不是定点类型。

不要使用 std::fixed,因为它无法将小的 double 打印成除了 0.000...000 之外的任何东西。对于大的 double,它会打印出许多数字,可能包括数百个可疑的信息。

std::cout << "std::fixed: " << std::fixed << exp (-100) << '\n';
std::cout << "std::fixed: " << std::fixed << exp (+100) << '\n';
// output
std::fixed: 0.000000
std::fixed: 26881171418161356094253400435962903554686976.000000 

为了获得完整的精度,请先使用 std::scientific,它会“以科学计数法写入浮点值”。请注意默认情况下小数点后的6位数字是不足够的,这将在下一个步骤中处理。

std::cout << "std::scientific: " << std::scientific << exp (-100) << '\n';  
std::cout << "std::scientific: " << std::scientific << exp (+100) << '\n';
// output
std::scientific: 3.720076e-44
std::scientific: 2.688117e+43

  1. 精度要求多高(总共有多少位数字)?

使用二进制基数2编码的double在各个2的幂之间编码相同的精度。通常为53位。

[1.0...2.0) 有2的53次方个不同的double,
[2.0...4.0) 有2的53次方个不同的double,
[4.0...8.0) 有2的53次方个不同的double,
[8.0...10.0) 有2/8 * 2的53次方个不同的double。

但是,如果用N个有效数字将代码打印为十进制数,则组合数[1.0...10.0)为9/10 * 10的N次方。

无论选择什么样的N(精度),都不会有一一对应的关系between double and decimal text。如果选择固定的N,有时候对于某些double的值,它可能略微超过或低于真正需要的数量。我们可以出现过少错误(下面是a))或过多错误(下面是b))。

有3个候选的N

a) 使用一个N,这样当从文本-double-文本进行转换时,所有double都可以得到相同的文本。

std::cout << dbl::digits10 << '\n';
// Typical output
15

b) 使用一个N,这样在从double-文本-double进行转换时,我们可以确保对于所有double,我们都会得到相同的double

// C++11
std::cout << dbl::max_digits10 << '\n';
// Typical output
17
max_digits10 不可用时,注意由于基数 2 和基数 10 属性,digits10 + 2 <= max_digits10 <= digits10 + 3,我们可以使用 digits10 + 3 确保打印出足够的十进制数字。

c)使用随值变化的 N

当代码想要显示最小文本(N == 1)或者一个 double 的精确值(例如对于 denorm_minN == 大约1000)时,这可能是有用的。然而,由于这是一项“工作”,不太可能是 OP 的目标,因此它将被搁置。


通常使用 b) 来“以完整精度打印 double 值”。一些应用程序可能更喜欢使用 a) 来避免提供过多信息而导致错误。

使用 .scientific.precision() 设置小数点后要打印的位数,因此将打印 1 + .precision() 位数字。代码需要 max_digits10 总数字,因此使用 max_digits10 - 1 调用 .precision()

typedef std::numeric_limits< double > dbl;
std::cout.precision(dbl::max_digits10 - 1);
std::cout << std::scientific <<  exp (-100) << '\n';
std::cout << std::scientific <<  exp (+100) << '\n';
// Typical output
3.7200759760208361e-44
2.6881171418161356e+43
//2345678901234567  17 total digits

类似的C语言问题


3
回答很好!不过有几点需要说明:你说得对,precision()会设置科学计数法输出的小数位数。如果没有指定scientific,它则会设置总位数,但不包括指数部分。根据数值大小,可能仍会得到科学计数输出,但此时输出的数字可能比你设置的位数少。例如:cout.precision(3); cout << 1.7976931348623158e+308; // "1.8e+308"。使用printf将得到不同的结果。这些都是需要注意的令人困惑的地方。 - Simpleton
1
为了后代,这里是使用printf保证所有双精度数字在科学计数法下确切字符串表示所需的缓冲区长度:char buf[DBL_DECIMAL_DIG + 3 + 5]; sprintf(buf, "%.*g", DBL_DECIMAL_DIG, d); 额外的字符用于:符号、小数点、尾随零、e[+|-]、指数的3个数字(DBL_MAX_10_EXP = 308)。因此,所需的总字符数为25。 - Simpleton
1
@IInspectable 确实所有有限FP都可以转换为十进制文本,但并不一定使用std::cout <<。只有极少数实现会对所有的std::cout.precision(n)进行转换。IEEE 754仅要求至少dbl::max_digits10 + 3。由于“双精度和十进制文本之间的一一映射”指的是两个转换方向,我们同意其中一个方向可能存在映射,而另一个方向则不存在。 - chux - Reinstate Monica
1
非常好,"X和Y之间的映射"确实暗示了双向关系,这似乎是我在留下评论时没注意到的。 - IInspectable
@Ruslan 关于应该是max_digits10而不是max_digits:--> 是的,用于总共的有效十进制数字。使用std::scientific1 + setprecision是打印的有效数字数。建议采用此答案的std::scientificcout.precision(dbl::max_digits10 - 1); - chux - Reinstate Monica
显示剩余2条评论

14

使用iostreams的方式有点笨重。我更喜欢使用boost::lexical_cast,因为它可以为我计算正确的精度。而且速度也很快

#include <string>
#include <boost/lexical_cast.hpp>

using boost::lexical_cast;
using std::string;

double d = 3.14159265358979;
cout << "Pi: " << lexical_cast<string>(d) << endl;

输出:

圆周率:3.14159265358979


Boost文档中提到:“对于具有std::numeric_limits特化的数字,当前版本现在选择匹配的精度”。这似乎是获得最大精度的最简单方法。(http://www.boost.org/doc/libs/1_58_0/doc/html/boost_lexical_cast/changes.html) - JDiMatteo
链接与boost :: lexical_cast(http://www.boost.org/doc/libs/release/libs/conversion/lexical_cast.htm)已失效。 - chux - Reinstate Monica
请注意,仅使用15个数字(例如“Pi: 3.14159265358979”)打印double可能无法转换回相同的double。要始终执行此操作,需要使用更多类似于max_digits10重要数字。 - chux - Reinstate Monica

11

所谓完全精确,是指足够准确地显示所需值的最佳近似值,但应指出,double使用基于2进制的表示法存储,而基于2进制不能准确表示像1.1这样微不足道的东西。获取实际双精度浮点数(没有任何舍入误差)的完整精度的唯一方法是打印二进制位(或十六进制数字)。

一种方法是使用一个uniondouble强制转换为整数,然后打印该整数,因为整数不会遭受截断或舍入问题。 (像这样的类型强制转换不受 C++ 标准支持,但它在 C 中得到支持。 但是,大多数 C++ 编译器可能仍然会正确打印出该值。我认为 g++ 支持此操作。)

union {
    double d;
    uint64_t u64;
} x;
x.d = 1.1;
std::cout << std::hex << x.u64;

这将为您提供双精度浮点数的100%准确精度... ,但是由于人类无法读取IEEE双精度格式,因此它将完全无法阅读!维基百科对如何解释二进制位有很好的介绍。

在较新的C++中,您可以这样做

std::cout << std::hexfloat << 1.1;

使用联合体的变量会导致未定义行为,因为它试图读取未初始化的值“x.u64”。 - user7860670

11

以下是显示完整精度的 double 型变量的方法:

double d = 100.0000000000005;
int precision = std::numeric_limits<double>::max_digits10;
std::cout << std::setprecision(precision) << d << std::endl;

这显示:

100.0000000000005


max_digits10是必要的数字位数,用于唯一表示所有不同的双精度浮点数值。max_digits10表示小数点前后的数字位数。


不要在std::fixed中使用set_precision(max_digits10)。
对于定点表示法,set_precision()仅设置小数点后的数字位数。这是不正确的,因为max_digits10表示小数点前后的数字位数。

double d = 100.0000000000005;
int precision = std::numeric_limits<double>::max_digits10;
std::cout << std::fixed << std::setprecision(precision) << d << std::endl;

这会显示错误的结果:

100.00000000000049738

注意:需要头文件

#include <iomanip>
#include <limits>

5
这是因为100.0000000000005没有被准确地表示为一个“double”类型的数值。(尽管看起来应该可以,但实际上不能,因为它会被规范化,也就是二进制形式的表示方法)。要验证这一点,请尝试执行以下操作:100.0000000000005 - 100。我们得到了4.973799150320701e-13 - Evgeni Sergeev

7

C++20 std::format

这个伟大的 C++ 新库特性的优点是不会像 std::setprecision 一样影响到 std::cout 的状态:

#include <format>
#include <string>

int main() {
    std::cout << std::format("{:.2} {:.3}\n", 3.1415, 3.1415);
}

期望输出:

3.14 3.142

https://dev59.com/-nRB5IYBdhLWcg3wro2B#65329803所述,如果您没有明确传递精度,则会打印具有回程保证的最短十进制表示。TODO更详细地了解它与https://dev59.com/-nRB5IYBdhLWcg3wro2B#554134中显示的dbl::max_digits10相比如何使用{:.{}}

#include <format>
#include <limits>
#include <string>

int main() {
    std::cout << std::format("{:.{}}\n",
        3.1415926535897932384626433, dbl::max_digits10);
}

另请参阅:


4

IEEE 754 浮点数采用基于 2 的表示方法来存储。任何基于 2 的数字都可以被表示为十进制(基于 10)的完全精度。然而,所有提出的答案都截断了小数部分。

这似乎是由于对 std::numeric_limits<T>::max_digits10 表示的误解造成的:

std::numeric_limits<T>::max_digits10 的值是必要的十进制数字的数量,以唯一地表示类型 T 的所有不同的值。

换句话说:这是输出的最坏情况下所需的数字位数,以便从二进制到十进制再到二进制进行回路处理,而不会丢失任何信息。如果您输出至少 max_digits10 个小数并重建一个浮点值,则保证得到与开始时完全相同的二进制表示。

重要的是:max_digits10 通常既不能产生最短的十进制数,也不足以表示完整的精度。我不知道 C++ 标准库中是否有一个常量来编码包含浮点值的完整精度所需的最大十进制位数。我相信对于 double 类型,这个数字大约是 7671。输出带有完整精度的浮点值的一种方法是使用足够大的精度值(例如)2,并让库去掉任何尾随零:

#include <iostream>

int main() {
    double d = 0.1;
    std::cout.precision(767);
    std::cout << "d = " << d << std::endl;
}

这将生成包含完整精度的以下输出:
d = 0.1000000000000000055511151231257827021181583404541015625

请注意,这个小数位数比 `max_digits10` 所示的要多得多。
虽然这回答了所问的问题,但更常见的目标是获得任何给定浮点值的最短十进制表示,保留所有信息。同样,我不知道有任何方法指示标准 I/O 库输出该值。从 C++17 开始,C++ 终于引入了 std::to_chars 以实现该转换。默认情况下,它生成保留所有信息的任何给定浮点值的最短十进制表示。
它的接口有些笨拙,您可能想将其封装到一个函数模板中,并返回一些可以输出到 `std::cout`(如 `std::string`)的内容,例如:
#include <charconv>
#include <array>
#include <string>
#include <system_error>

#include <iostream>
#include <cmath>

template<typename T>
std::string to_string(T value)
{
    // 24 characters is the longest decimal representation of any double value
    std::array<char, 24> buffer {};
    auto const res { std::to_chars(buffer.data(), buffer.data() + buffer.size(), value) };
    if (res.ec == std::errc {})
    {
        // Success
        return std::string(buffer.data(), res.ptr);
    }

    // Error
    return { "FAILED!" };
}

int main()
{
    auto value { 0.1f };
    std::cout << to_string(value) << std::endl;
    value = std::nextafter(value, INFINITY);
    std::cout << to_string(value) << std::endl;
    value = std::nextafter(value, INFINITY);
    std::cout << to_string(value) << std::endl;
}

这将打印出(使用 Microsoft 的 C++ 标准库):
0.1
0.10000001
0.10000002

1 这是根据Stephan T. Lavavej在CppCon 2019演讲中的内容,名为浮点数 <charconv>:使用C++17的终极武器使代码快10倍。(整个演讲值得一看。)

2 这也需要使用科学计数法(scientific)和定点数(fixed)的组合,以较短的方式表示。我不知道如何使用C ++标准I/O库来设置此模式。


@chu 这假设最小可表示值也是十进制中具有最长数字序列的值。这听起来很合理,但浮点数值并不完全适用于合理性。您是否尝试使用 nextafter 来查看在 DBL_TRUE_MIN 附近长度是否增加? - IInspectable
@chu 啊,没错,DBL_TRUE_MIN 只在尾数中设置了最低有效位。我之前没有想到这一点。不过,我仍然需要看到一个数学证明才能理解为什么会导致最长的十进制序列。 - IInspectable
注意:输出浮点数的完整精度的一种方法是使用足够大的精度值,符合IEEE 754标准的库只需要打印正确舍入的值到 long double::max_digits10 + 3个有效数字。我们可能无法获得完整的精度。 - chux - Reinstate Monica
“我需要看到数学证明才能理解。”听起来像是某个网站上的好问题,但要履行它需要一些工作,对于一个快速评论来说有点太多了。 - chux - Reinstate Monica
@chu "符合IEEE 754标准的库只需要将正确舍入的值打印到long double::max_digits10 + 3个有效数字。" - 我在C++标准文本中没有找到这个要求。这是从C语言中引用的吗?如果是,您知道我可以在哪里找到吗? - IInspectable
显示剩余2条评论

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