在我之前的问题中,我正在打印一个在我没有预料到的情况下被四舍五入的double
使用。cout
如何使用全精度进行cout
打印?double
16 回答
您可以直接设置精度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;
#include <iomanip>
std::cout << std::setprecision (15) << 3.14159265358979 << std::endl;
这是我会使用的:
std::cout << std::setprecision (std::numeric_limits<double>::digits10 + 1)
<< 3.14159265358979
<< std::endl;
基本上,limits 包具有所有内置类型的特征。
浮点数(float/double/long double)的特征之一是digits10属性。这定义了以 10 为底的浮点数的准确性(我忘记了确切的术语)。
有关其他属性的详细信息,请参见:http ://www.cplusplus.com/reference/std/limits/numeric_limits.html。
在 C++20 中,您将能够使用它std::format
来执行此操作:
std::cout << std::format("{}", M_PI);
输出(假设 IEEE754 double
):
3.141592653589793
默认浮点格式是具有往返保证的最短十进制表示。与 I/O 操纵器相比,这种方法的优势setprecision
在于它不会打印不必要的数字。
同时你可以使用基于的 {fmt}库std::format
。{fmt} 还提供了print
使这更容易和更高效的功能(godbolt):
fmt::print("{}", M_PI);
免责声明:我是 {fmt} 和 C++20 的作者std::format
。
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
如何
double
使用 cout 以全精度打印值?
使用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) 精度之一。太多精确的答案不能提供所需的正确值。因此,这是对一个老问题的回答。
- 什么基础?
Adouble
肯定是使用 base 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
- 否则:
fixed
或scientific
?
Adouble
是浮点类型,而不是定点。
请勿使用,因为它无法std::fixed
打印小double
,但0.000...000
. 对于 large 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
- 多少精度(总位数)?
double
使用二进制基数 2 编码的A在 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
之间都不会存在一对一的映射。double
如果选择了一个固定值,有时它会比某些值N
真正需要的稍多或少一些。double
我们可能会在太少(a)
下)或太多(b)
下)上出错。
3名候选人N
:
a) 在从 text--text 转换时使用N
so,double
我们会为 all 得到相同的文本double
。
std::cout << dbl::digits10 << '\n';
// Typical output
15
b)N
在从double
-text-转换时使用 so,我们对 alldouble
得出相同的结果。double
double
// C++11
std::cout << dbl::max_digits10 << '\n';
// Typical output
17
当max_digits10
不可用时,请注意,由于 base 2 和 base 10 属性digits10 + 2 <= max_digits10 <= digits10 + 3
,我们可以使用digits10 + 3
来确保打印足够的十进制数字。
c) 使用N
随值变化的 an。
当代码想要显示最少的文本 ( N == 1
) 或 a 的确切值double
(N == 1000-ish
在 的情况下denorm_min
)时,这可能很有用。然而,由于这是“工作”并且不太可能是 OP 的目标,因此它将被搁置一旁。
通常是 b) 用于“double
以全精度打印值”。一些应用程序可能更喜欢 a) 在不提供太多信息时出错。
使用.scientific
,.precision()
设置要在小数点后打印的位数,因此1 + .precision()
会打印数字。代码需要max_digits10
总位数,因此.precision()
用max_digits10 - 1
.
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
以下是如何以全精度显示双精度:
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 表示小数点前后的位数。
不要将 set_precision(max_digits10) 与 std::fixed 一起使用。
在固定表示法上, 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>
通过全精度,我假设有足够的精度来显示预期值的最佳近似值,但应该指出的double
是,使用 base 2 表示存储,base 2 不能1.1
准确地表示微不足道的东西。获得实际双精度的唯一方法(没有 ROUND OFF 错误)是打印出二进制位(或十六进制 nybbles)。
这样做的一种方法是使用 aunion
将其键入double
到整数,然后打印整数,因为整数不会受到截断或舍入问题的影响。(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;
C++20std::format
这个伟大的新 C++ 库功能具有不影响状态的std::cout
优点std::setprecision
:
#include <format>
#include <string>
int main() {
std::cout << std::format("{:.2} {:.3}\n", 3.1415, 3.1415);
}
预期输出:
3.14 3.142
如https://stackoverflow.com/a/65329803/895245所述,如果您没有明确传递精度,它将打印具有往返保证的最短十进制表示。TODO 更详细地了解它与以下内容的比较:如dbl::max_digits10
https://stackoverflow.com/a/554134/895245所示{:.{}}
:
#include <format>
#include <limits>
#include <string>
int main() {
std::cout << std::format("{:.{}}\n",
3.1415926535897932384626433, dbl::max_digits10);
}
也可以看看:
IEEE 754浮点值使用 base 2 表示存储。任何以 2 为底的数字都可以表示为完全精度的小数(以 10 为底)。然而,所有提议的答案都没有。它们都截断十进制值。
这似乎是由于对std::numeric_limits<T>::max_digits10
代表的误解:
的值
std::numeric_limits<T>::max_digits10
是唯一表示 type 的所有不同值所必需的以 10 为基数的位数T
。
换句话说:如果你想从二进制到十进制再到二进制,它是输出所需的(最坏情况)位数,而不会丢失任何信息。如果您至少输出max_digits10
小数并重建浮点值,则可以保证获得与开始时完全相同的二进制表示。
重要的是:max_digits10
通常既不会产生最短的小数,也不足以表示完整的精度。我不知道 C++ 标准库中有一个常量,它编码包含浮点值的完整精度所需的最大十进制数字。我相信这类似于double
s 1的 767 。以全精度输出浮点值的一种方法是使用足够大的精度值,例如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 开始,进行这种转换的可能性终于以std::to_chars
. 默认情况下,它会生成保留全部信息的任何给定浮点值的最短十进制表示。
它的界面有点笨拙,您可能希望将其包装到一个函数模板中,该模板返回您可以输出的std::cout
内容(如 a 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 演讲,标题为Floating-Point <charconv>: Making Your Code 10x Faster With C++17's Final Boss。(整个演讲值得一看。)
2 这还需要使用 和 的组合scientific
,fixed
以较短者为准。我不知道使用 C++ 标准 I/O 库设置此模式的方法。
printf("%.12f", M_PI);
%.12f 表示浮点数,精度为 12 位。
最便携...
#include <limits>
using std::numeric_limits;
...
cout.precision(numeric_limits<double>::digits10 + 1);
cout << d;
在这个问题中,有一个关于如何无损地将双精度转换为字符串的描述(在 Octave 中,但它可以在 C++ 中轻松复制)。想法是对浮点数进行简短的人类可读描述和六进制形式的无损描述,例如:pi -> 3.14{54442d18400921fb}。
这将在点后显示最多两位小数的值。
#include <iostream>
#include <iomanip>
double d = 2.0;
int n = 2;
cout << fixed << setprecision(n) << d;
见这里: 定点符号
使用固定浮点表示法 将 str 流的浮点字段格式标志设置为固定。
当 floatfield 设置为 fixed 时,浮点值使用定点表示法写入:该值由精度字段 (precision) 指定的小数部分中的位数表示,并且没有指数部分。
设置小数精度 设置用于格式化输出操作的浮点值的小数精度。
如果您熟悉表示浮点数的 IEEE 标准,您就会知道在标准范围之外不可能以全精度显示浮点数,也就是说,它总是会导致实际值的四舍五入。
您需要首先检查该值是否在范围内,如果是,则使用:
cout << defaultfloat << d ;
使用默认浮点表示法 将 str 流的 floatfield 格式标志设置为 defaultfloat。
当 floatfield 设置为 defaultfloat 时,浮点值使用默认表示法写入:表示使用尽可能多的有意义的数字,直到流的小数精度(precision),计算小数点之前和之后的数字(如果有)。
这也是 的默认行为cout
,这意味着您不会显式使用它。
这是一个适用于任何浮点类型的函数,而不仅仅是double
,它还可以将流恢复为后来找到的方式。不幸的是,它不能与线程很好地交互,但这就是 iostreams 的本质。您需要在文件开头包含以下内容:
#include <limits>
#include <iostream>
这是函数,如果你经常使用它,你可以在头文件中使用它:
template <class T>
void printVal(std::ostream& os, T val)
{
auto oldFlags = os.flags();
auto oldPrecision = os.precision();
os.flags(oldFlags & ~std::ios_base::floatfield);
os.precision(std::numeric_limits<T>::digits10);
os << val;
os.flags(oldFlags);
os.precision(oldPrecision);
}
像这样使用它:
double d = foo();
float f = bar();
printVal(std::cout, d);
printVal(std::cout, f);
如果您希望能够使用正常的插入<<
运算符,您可以使用这个额外的包装代码:
template <class T>
struct PrintValWrapper { T val; };
template <class T>
std::ostream& operator<<(std::ostream& os, PrintValWrapper<T> pvw) {
printVal(os, pvw.val);
return os;
}
template <class T>
PrintValWrapper<T> printIt(T val) {
return PrintValWrapper<T>{val};
}
现在你可以像这样使用它:
double d = foo();
float f = bar();
std::cout << "The values are: " << printIt(d) << ", " << printIt(f) << '\n';
使用 ostream::precision(int)
cout.precision( numeric_limits<double>::digits10 + 1);
cout << M_PI << ", " << M_E << endl;
将产生
3.141592653589793, 2.718281828459045
为什么你必须说“+1”我不知道,但你得到的额外数字是正确的。