为什么这个使用余弦的数值方程在控制台应用程序和 windows 应用程序之间产生不同的结果?
Why does this numeric equation using a cosine produce a different result between a console application and windows application?
我写了一个数学函数作为优化算法中的基准函数。
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;
}
但当输入为 SolomonFunction(new double[] { -4.74641638144941E+151, -6.49440696607247E+153, -1.0998592442531E+153, 3.58027097738642E+149, 6.28490996716059E+152 })
时,它在控制台应用程序和 windows 应用程序中有不同的结果
在控制台应用程序中,结果是 6,616968044816507E+152
在 windows 应用程序中,结果是 -4,09139395927863E+154
是否需要在 windows 应用程序中执行一些我不需要在控制台应用程序中执行的不同操作?还是我从根本上误解了什么?
在生成“-4,09139395927863E+154”的平台中,Math.Cos
的套路被打破了。它显然使用了不支持 [−2−63, +2−63].
之外的操作数的处理器指令
由于我不使用 C#,这里有一个重现正确行为的 C 程序:
#include <math.h>
#include <stdio.h>
static double SolomonFunction(size_t length, double *x)
{
double sum = 0;
for (int i = 0; i < length; i++)
sum += x[i] * x[i];
return 1 - cos(2 * M_PI * sqrt(sum)) + 0.1 * sqrt(sum);
}
#define NumberOf(a) (sizeof (a) / sizeof *(a))
int main(void)
{
double x[] = { -4.74641638144941E+151, -6.49440696607247E+153, -1.0998592442531E+153, 3.58027097738642E+149, 6.28490996716059E+152 };
printf("%.16g\n", SolomonFunction(NumberOf(x), x));
}
当 运行 在 macOS 10.14.6 上使用 Apple Clang 11 时,会生成“6.616968044816507e+152”。看一下计算,可以看出sum
一定很大,结果应该完全被0.1 * Math.Sqrt(sum)
支配了。由于 [−1, +1] 中实数的余弦值域,公式的 1 - Math.Cos(…)
部分的影响可以忽略不计,无论 Math.Cos
的参数如何。所以这似乎是一个合理的结果。
考虑到另一个结果,“-4,09139395927863E+154”,我们看到公式在正确计算时不可能产生负结果。 1 - Math.Cos(…)
应该在 [0, 2] 之间,而 0.1 * Math.Sqrt(sum)
永远不应该是负数,所以它们的总和应该是 non-negative.
这个不正确的结果完全可以用有缺陷的 Math.Cos
来解释。假设,当参数很大时,Math.Cos
returns 它的参数而不是它的余弦。我们可以使用 return 1 - (2 * M_PI * sqrt(sum)) + 0.1 * sqrt(sum);
在上面的 C 代码中重现这一点,其中 cos
已被删除,只留下它的参数。 运行 这会产生输出“-4.091393959278625e+154”,与报告的输出匹配(四舍五入到不同的位数),证实了假设。
这与 FCOS
指令的行为一致。 Intel 64 和 IA-32 架构软件开发人员手册,合卷,2017 年 12 月,第 906 页,对于 FCOS
:
If the source operand is outside the acceptable range, the C2 flag in the FPU status word is set, and the value in register ST(0) remains unchanged.
因此,当余弦参数超出支持范围(-263至+263)时,执行FCOS
将参数留在寄存器中,该寄存器也用于结果。然后 Math.Cos
显然使用这个值作为结果。
我写了一个数学函数作为优化算法中的基准函数。
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;
}
但当输入为 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 应用程序中执行一些我不需要在控制台应用程序中执行的不同操作?还是我从根本上误解了什么?
在生成“-4,09139395927863E+154”的平台中,Math.Cos
的套路被打破了。它显然使用了不支持 [−2−63, +2−63].
由于我不使用 C#,这里有一个重现正确行为的 C 程序:
#include <math.h>
#include <stdio.h>
static double SolomonFunction(size_t length, double *x)
{
double sum = 0;
for (int i = 0; i < length; i++)
sum += x[i] * x[i];
return 1 - cos(2 * M_PI * sqrt(sum)) + 0.1 * sqrt(sum);
}
#define NumberOf(a) (sizeof (a) / sizeof *(a))
int main(void)
{
double x[] = { -4.74641638144941E+151, -6.49440696607247E+153, -1.0998592442531E+153, 3.58027097738642E+149, 6.28490996716059E+152 };
printf("%.16g\n", SolomonFunction(NumberOf(x), x));
}
当 运行 在 macOS 10.14.6 上使用 Apple Clang 11 时,会生成“6.616968044816507e+152”。看一下计算,可以看出sum
一定很大,结果应该完全被0.1 * Math.Sqrt(sum)
支配了。由于 [−1, +1] 中实数的余弦值域,公式的 1 - Math.Cos(…)
部分的影响可以忽略不计,无论 Math.Cos
的参数如何。所以这似乎是一个合理的结果。
考虑到另一个结果,“-4,09139395927863E+154”,我们看到公式在正确计算时不可能产生负结果。 1 - Math.Cos(…)
应该在 [0, 2] 之间,而 0.1 * Math.Sqrt(sum)
永远不应该是负数,所以它们的总和应该是 non-negative.
这个不正确的结果完全可以用有缺陷的 Math.Cos
来解释。假设,当参数很大时,Math.Cos
returns 它的参数而不是它的余弦。我们可以使用 return 1 - (2 * M_PI * sqrt(sum)) + 0.1 * sqrt(sum);
在上面的 C 代码中重现这一点,其中 cos
已被删除,只留下它的参数。 运行 这会产生输出“-4.091393959278625e+154”,与报告的输出匹配(四舍五入到不同的位数),证实了假设。
这与 FCOS
指令的行为一致。 Intel 64 和 IA-32 架构软件开发人员手册,合卷,2017 年 12 月,第 906 页,对于 FCOS
:
If the source operand is outside the acceptable range, the C2 flag in the FPU status word is set, and the value in register ST(0) remains unchanged.
因此,当余弦参数超出支持范围(-263至+263)时,执行FCOS
将参数留在寄存器中,该寄存器也用于结果。然后 Math.Cos
显然使用这个值作为结果。