在编译器之间从 printf() 获得一致的输出

Sza*_*lcs 3 c printf visual-c++

我注意到printf("%#g\n", 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)上看不到。哪些特定的编译器版本/标准库版本/操作系统受到影响?
  • 有没有办法在编译器之间获得一致的输出?

Eri*_*hil 7

这是一个错误

您在 Visual Studio 中使用的 C 实现有缺陷。以下引文来自 C 2018。相关文本在 2011 年和 1999 年标准中实际上相同(在 1999 年,不等式使用文本而不是使用“>”和“?”的数学符号来描述)。

首先,在这种情况下,#意味着将生成小数点字符并且不会删除尾随零。它对删除尾随零之前应生成的位数没有影响。7.21.6.1 6 说“……对于a, A, e, E, f, F, g, 和G转换,转换浮点数的结果总是包含一个小数点字符,即使后面没有数字……对于gG转换,尾随零不是从结果中删除……”这使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;
}
Run Code Online (Sandbox Code Playgroud)

这假设我们在精度 6 和 8 中看到的相同错误存在于精度 1 中。如果不是,则可以轻松进行适当的调整。