Escribo una función matemática para que sea una función de referencia en mi algoritmo de optimización.
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; }
pero tiene un resultado diferente en la aplicación de consola y en la aplicación de Windows cuando la entrada es SolomonFunction(new double[] { -4.74641638144941E+151, -6.49440696607247E+153, -1.0998592442531E+153, 3.58027097738642E+149, 6.28490996716059E+152 })
en la aplicación de consola el resultado es 6,616968044816507E+152
en la aplicación de Windows el resultado es -4,09139395927863E+154
¿Hay algo diferente que deba hacer en una aplicación de Windows que no deba hacer en una aplicación de consola? ¿O estoy fundamentalmente malinterpretando algo?
En la plataforma que produce “-4,09139395927863E+154”, la rutina Math.Cos
está rota. Aparentemente, usa una instrucción de procesador que no admite operandos fuera de [−2 −63 , +2 −63 ].
Como no uso C#, aquí hay un programa en C que reproduce el comportamiento correcto:
#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)); }
Cuando se ejecuta con Apple Clang 11 en macOS 10.14.6, se produce "6.616968044816507e+152". Mirando los cálculos, podemos ver que la sum
debe ser enorme y el resultado debe estar completamente dominado por 0.1 * Math.Sqrt(sum)
. Dado que el rango del coseno de los números reales en [−1, +1], la parte 1 - Math.Cos(…)
de la fórmula debería tener un efecto insignificante, independientemente del argumento de Math.Cos
. Así que esto parece un resultado razonable.
Considerando el otro resultado, “-4,09139395927863E+154”, vemos que es imposible que la fórmula produzca un resultado negativo cuando se calcula correctamente. 1 - Math.Cos(…)
debe estar en [0, 2], y 0.1 * Math.Sqrt(sum)
nunca debe ser negativo, por lo que su suma no debe ser negativa.
Este resultado incorrecto se explica completamente por un Math.Cos
defectuoso. Supongamos que, cuando el argumento es enorme, Math.Cos
devuelve su argumento en lugar de su coseno. Podemos reproducir esto con el código C anterior usando return 1 - (2 * M_PI * sqrt(sum)) + 0.1 * sqrt(sum);
, donde cos
ha sido eliminado, dejando solo su argumento. Ejecutar esto produce el resultado "-4.091393959278625e+154", que coincide con el resultado informado (con redondeo a un número diferente de dígitos), lo que confirma la hipótesis.
Esto es consistente con el comportamiento de la instrucción FCOS
. Intel 64 and IA-32 Architecture Software Developer's Manual, volúmenes combinados, diciembre de 2017, página 906, dice, para FCOS
:
Si el operando de origen está fuera del rango aceptable, se establece el indicador C2 en la palabra de estado de FPU y el valor en el registro ST(0) permanece sin cambios.
Por lo tanto, cuando el argumento del coseno está fuera del rango admitido (−2 63 a +2 63 ), la ejecución de FCOS
deja el argumento en el registro que también se usa para el resultado. Entonces Math.Cos
aparentemente usa este valor para el resultado.