在编译器之间从printf()获得一致的输出
我注意到printf("%#gn", 0.0)任何 gcc/clang 版本与 Windows 7 上的 Visual Studio 2019(截至今天的最新版本)都会提供不同的输出。
gcc/clang 给出0.00000(总共 6 位数字,在 之后 5 位.)而 VS 给出0.000000(总共 7 位,在 之后 6 位.)。同样,"%#.8g"gcc/clang 总共给出 8 位数字,VS 总共给出 9 位数字。
问题:
- 标准对此有何看法?编译器/标准库之一是否有问题?
- 我只在本地(Windows 7)的 VS 中看到这种行为,但在 Azure Pipelines(最近的 Windows Server)上看不到。哪些特定的编译器版本/标准库版本/操作系统受到影响?
- 有没有办法在编译器之间获得一致的输出?
回答
这是一个错误
您在 Visual Studio 中使用的 C 实现有缺陷。以下引文来自 C 2018。相关文本在 2011 年和 1999 年标准中实际上相同(在 1999 年,不等式使用文本而不是使用“>”和“?”的数学符号来描述)。
首先,在这种情况下,#意味着将生成小数点字符并且不会删除尾随零。它对删除尾随零之前应生成的位数没有影响。7.21.6.1 6 说“……对于a, A, e, E, f, F, g, 和G转换,转换浮点数的结果总是包含一个小数点字符,即使后面没有数字……对于g和G转换,尾随零不是从结果中删除……”这使g规范中说“……除非# 使用标志,任何尾随零将从结果的小数部分中删除,如果没有小数部分剩余,小数点字符将被删除。”
其次,对于零值,g格式规则说明使用该f格式。这是因为g(or G)的规则取决于e格式将使用的指数和要求的精度:
- 对于
e,7.21.6.1 8 表示“......如果值为零,则指数为零。” - 对于
g,它表示“……如果非零,则P等于精度,如果省略精度,则为 6,如果精度为零,则为 1……”因此,问题中的或给出的P为 6 或 8 。%#g%#.8g - 文本继续“……如果P > X ? ?4、转换是用样式
f(或F)和精度P?( X + 1)。”
因此,对于在转换%#g或%#.8g与风格进行f采用精密6α(0 + 1)= 5或8?(0 + 1)分别= 7。
第三,对于f7.21.6.1 8 说“double表示浮点数的参数被转换为[-]ddd.ddd样式的十进制表示法,其中小数点字符后的位数等于精度规范……”因此,应分别在小数点后打印 5 或 7 位数字。
因此,对于%#g,“0.00000”符合 C 标准而“0.000000”不符合。而对于,,%#.8g八位数字(小数点后七位)符合,九位数字(八位后)不符合。
因为你用visual-c++标记了这个,我会注意到C++标准采用了C规范printf。C 2017 草案 N4659 20.2 说“C++ 标准库还提供了 C 标准库的功能,经过适当调整以确保静态类型安全。”
补偿错误
这个bug很可能是在C/C++库中,而不是编译器,所以通过使用例如#if微软宏的值来调整源代码_MSC_VER可能不是一个好的解决方案。(特别是,在编译之后,编译器可能会与更高版本的库一起运行。)
人们可能会在程序启动期间测试库。int PrecisionAdjustment;使用外部作用域定义后,可以使用此代码对其进行初始化:
{
/* The following tests for a Microsoft bug in which a "%#g" conversion
produces one more digit than the C standard specifies. According to
the standard, formatting zero with "%#.1g" should produce "0.", but
Microsoft software has been observed to produce "0.0". If the bug
appears to be present, PrecisionAdjustment is set -1. Otherwise,
it is 0. This can then be used to select which format string to
use or to adjust a dynamic precision given with "*" such as:
printf("%#.*g", 6+PrecisionAdjustment, value);
*/
char buffer[4];
snprintf(buffer, sizeof buffer, "%#.1g", 0.);
PrecisionAdjustment = buffer[2] == '0' ? -1 : 0;
}
这假设我们在精度 6 和 8 中看到的相同错误存在于精度 1 中。如果不是,则可以轻松进行适当的调整。