为什么在 C++17 中使用十六进制浮点常量?
Why hexadecimal floating constants in C++17?
C++17添加十六进制浮点常量(floating point literal)。为什么?举几个例子说明好处怎么样。
浮点数存储在 x86/x64 处理器中,基数为 2,而不是基数为 10:https://en.wikipedia.org/wiki/Double-precision_floating-point_format。由于无法准确表示许多十进制浮点数,例如十进制 0.1 可以表示为 0.1000000000000003 或 0.0999999999999997 - 任何具有足够接近十进制 0.1 的基数 2 表示形式。由于这种不精确性,例如以十进制打印然后解析浮点数可能会导致与打印前以二进制方式存储在内存中的数字略有不同。
对于某些应用程序来说,出现此类错误是不可接受的:它们希望解析为与打印前的二进制浮点数完全相同的二进制浮点数(例如,一个应用程序导出浮点数据而另一个应用程序导入)。为此,可以以十六进制格式导出和导入双精度数。因为16是2的幂,二进制浮点数可以用16进制表示。
printf
和 scanf
已使用 %a
格式说明符进行扩展,允许打印和解析十六进制浮点数。虽然MSVC++ does not support %a
format specifier for scanf
还:
The a and A specifiers (see printf Type Field Characters) are not available with scanf.
要以十六进制格式以全精度打印 double
,应指定在点后打印 13 个十六进制数字,对应于 13*4=52 位:
double x = 0.1;
printf("%.13a", x);
查看有关 hexadecimal floating point with code and examples 的更多详细信息(请注意,至少对于 MSVC++ 2013,printf
中 %a
的简单规范在点后打印 6 个十六进制数字,而不是 13 - 这在文末)。
特别是常量,如问题中所问,十六进制常量可能便于在精确的硬编码浮点输入上测试应用程序。例如。您的错误可能可重现为 0.1000000000000003,但不能重现为 0.0999999999999997,因此您需要十六进制硬编码值来指定十进制 0.1 的兴趣表示形式。
在小数上使用十六进制浮点数的两个主要原因是准确性和速度。
十进制常数与浮点数的底层二进制格式之间精确转换的算法是surprisingly complicated, and even nowadays conversion errors still occasionally arise。
十六进制和二进制之间的转换是一项更简单的工作,并且保证准确。一个示例用例是当您使用特定的浮点数而不是任何一方(例如,对于 exp
等特殊函数的实现)至关重要时。这种简单性还使转换速度更快(不需要任何中间 "bignum" 算术):在某些情况下,我看到 read/write 十六进制浮点数与小数运算的速度提高了 3 倍。
C++17添加十六进制浮点常量(floating point literal)。为什么?举几个例子说明好处怎么样。
浮点数存储在 x86/x64 处理器中,基数为 2,而不是基数为 10:https://en.wikipedia.org/wiki/Double-precision_floating-point_format。由于无法准确表示许多十进制浮点数,例如十进制 0.1 可以表示为 0.1000000000000003 或 0.0999999999999997 - 任何具有足够接近十进制 0.1 的基数 2 表示形式。由于这种不精确性,例如以十进制打印然后解析浮点数可能会导致与打印前以二进制方式存储在内存中的数字略有不同。
对于某些应用程序来说,出现此类错误是不可接受的:它们希望解析为与打印前的二进制浮点数完全相同的二进制浮点数(例如,一个应用程序导出浮点数据而另一个应用程序导入)。为此,可以以十六进制格式导出和导入双精度数。因为16是2的幂,二进制浮点数可以用16进制表示。
printf
和 scanf
已使用 %a
格式说明符进行扩展,允许打印和解析十六进制浮点数。虽然MSVC++ does not support %a
format specifier for scanf
还:
The a and A specifiers (see printf Type Field Characters) are not available with scanf.
要以十六进制格式以全精度打印 double
,应指定在点后打印 13 个十六进制数字,对应于 13*4=52 位:
double x = 0.1;
printf("%.13a", x);
查看有关 hexadecimal floating point with code and examples 的更多详细信息(请注意,至少对于 MSVC++ 2013,printf
中 %a
的简单规范在点后打印 6 个十六进制数字,而不是 13 - 这在文末)。
特别是常量,如问题中所问,十六进制常量可能便于在精确的硬编码浮点输入上测试应用程序。例如。您的错误可能可重现为 0.1000000000000003,但不能重现为 0.0999999999999997,因此您需要十六进制硬编码值来指定十进制 0.1 的兴趣表示形式。
在小数上使用十六进制浮点数的两个主要原因是准确性和速度。
十进制常数与浮点数的底层二进制格式之间精确转换的算法是surprisingly complicated, and even nowadays conversion errors still occasionally arise。
十六进制和二进制之间的转换是一项更简单的工作,并且保证准确。一个示例用例是当您使用特定的浮点数而不是任何一方(例如,对于 exp
等特殊函数的实现)至关重要时。这种简单性还使转换速度更快(不需要任何中间 "bignum" 算术):在某些情况下,我看到 read/write 十六进制浮点数与小数运算的速度提高了 3 倍。