为什么这个使用余弦的数值方程在控制台应用程序和 Windows 应用程序之间产生不同的结果?

muh*_*rom 5 c# math floating-point .net-core visual-studio-2019

我编写了一个数学函数作为我的优化算法中的基准函数。

public static double SolomonFunction(double[] x)
    {
        double f;
        double sum = 0;
        for (int i = 0; i < x.Length; i++)
        {
            sum += x[i] * x[i];
        }

        f = 1 - Math.Cos(2 * Math.PI * Math.Sqrt(sum)) + 0.1 * Math.Sqrt(sum);
        return f;
    }
Run Code Online (Sandbox Code Playgroud)

但当输入为时,它在控制台应用程序和 Windows 应用程序中具有不同的结果SolomonFunction(new double[] { -4.74641638144941E+151, -6.49440696607247E+153, -1.0998592442531E+153, 3.58027097738642E+149, 6.28490996716059E+152 })

在控制台应用程序中,结果是6,616968044816507E+152 在 Windows 应用程序中,结果是-4,09139395927863E+154

是否有一些我需要在 Windows 应用程序中执行但在控制台应用程序中不需要执行的操作?或者我从根本上误解了什么?

Eri*_*hil 6

在生成 \xe2\x80\x9c-4,09139395927863E+154\xe2\x80\x9d 的平台中,例程Math.Cos被破坏。它显然使用不支持 [\xe2\x88\x922 \xe2\x88\x9263 , +2 \xe2\x88\x9263 ] 之外的操作数的处理器指令。

\n

由于我不使用 C#,因此这里有一个重现正确行为的 C 程序:

\n
#include <math.h>\n#include <stdio.h>\n\nstatic double SolomonFunction(size_t length, double *x)\n{\n    double sum = 0;\n    for (int i = 0; i < length; i++)\n        sum += x[i] * x[i];\n\n    return 1 - cos(2 * M_PI * sqrt(sum)) + 0.1 * sqrt(sum);\n}\n\n\n#define NumberOf(a) (sizeof (a) / sizeof *(a))\n\n\nint main(void)\n{\n    double x[] = { -4.74641638144941E+151, -6.49440696607247E+153, -1.0998592442531E+153, 3.58027097738642E+149, 6.28490996716059E+152 };\n    printf("%.16g\\n", SolomonFunction(NumberOf(x), x));\n}\n
Run Code Online (Sandbox Code Playgroud)\n

当在 macOS 10.14.6 上与 Apple Clang 11 一起运行时,会生成 \xe2\x80\x9c6.616968044816507e+152\xe2\x80\x9d。从计算结果来看,我们可以看到它sum一定很大,而且结果应该完全由 主导0.1 * Math.Sqrt(sum)。由于实数的余弦范围在 [\xe2\x88\x921, +1] 中,因此无论 的1 - Math.Cos(\xe2\x80\xa6)参数如何,公式的这一部分应该具有可以忽略不计的影响Math.Cos。所以这似乎是一个合理的结果。

\n

考虑到另一个结果 \xe2\x80\x9c-4,09139395927863E+154\xe2\x80\x9d,我们发现该公式在正确计算时不可能产生负结果。1 - Math.Cos(\xe2\x80\xa6)应该在 [0, 2] 中,并且0.1 * Math.Sqrt(sum)永远不应该为负数,因此它们的总和应该是非负数。

\n

这个不正确的结果完全可以用缺陷来解释Math.Cos。假设当参数很大时,Math.Cos返回其参数而不是余弦。我们可以使用上面的 C 代码重现这一点return 1 - (2 * M_PI * sqrt(sum)) + 0.1 * sqrt(sum);,其中cos已被删除,仅保留其参数。运行此命令会生成输出 \xe2\x80\x9c-4.091393959278625e+154\xe2\x80\x9d,与报告的输出匹配(四舍五入到不同的位数),从而证实了假设。

\n

这与指令的行为一致FCOS。Intel 64 和 IA-32 架构软件开发人员\xe2\x80\x98s 手册,合并卷,2017 年 12 月,第 906 页,表示FCOS

\n
\n

如果源操作数超出可接受的范围,则 FPU 状态字中的 C2 标志被置位,并且寄存器 ST(0) 中的值保持不变。

\n
\n

因此,当余弦参数超出支持的范围(\xe2\x88\x922 63到 +2 63)时,执行FCOS会将参数保留在也用于结果的寄存器中。然后Math.Cos显然使用这个值作为结果。

\n