大整数 Math.Cos() 的精度

Fra*_*une 5 c# floating-point

我正在尝试在 C# 中计算 4203708359 弧度的余弦:

var x = (double)4203708359;
var c = Math.Cos(x);
Run Code Online (Sandbox Code Playgroud)

(4203708359可以用双精度精确表示。)

我越来越

c = -0.57977754519440394
Run Code Online (Sandbox Code Playgroud)

Windows 的计算器给出

c = -0.579777545198813380788467070278
Run Code Online (Sandbox Code Playgroud)

Linux 上的PHPcos(double)函数(内部仅使用cos(double)C 标准库)给出:

c = -0.57977754519881
Run Code Online (Sandbox Code Playgroud)

cos(double)使用 Visual Studio 2017 编译的简单 C 程序中的C函数给出

c = -0.57977754519881342
Run Code Online (Sandbox Code Playgroud)

Math.cos()以下是C# 中的定义:https: //github.com/dotnet/coreclr/blob/master/src/mscorlib/src/System/Math.cs#L57-L58

它似乎是一个内置函数。我(还)没有深入研究 C# 编译器来检查其有效编译的内容,但这可能是下一步。

同时:

为什么我的 C# 示例中的精度这么差,我该怎么办?

难道只是因为 C# 编译器中的余弦实现无法很好地处理大整数输入吗?

编辑 1:Wolfram Mathematica 11.0:

In[1] := N[Cos[4203708359], 50]
Out[1] := -0.57977754519881338078846707027800171954257546099993
Run Code Online (Sandbox Code Playgroud)

编辑2:我确实需要这种级别的精度,并且我已经准备好为了获得它而走很远。如果存在一个支持余弦的好库(到目前为止我的努力还没有实现),我很乐意使用任意精度库。

编辑3:我在 coreclr 的问题跟踪器上发布了问题: https: //github.com/dotnet/coreclr/issues/12737

Kev*_*vin 2

我想我可能知道答案。我很确定 sin/cos 库不会采用任意大的数字并计算它们的 sin/cos - 相反,它们会将它们减少到较低的数字(0-2xpi 之间?)并在那里计算它们。我的意思是,cos(x) = cos(x + 2xpi) = cos(x + 4xpi) = ...

问题是,程序如何减少你的 10 位数字呢?实际上,它应该计算出需要乘以多少次 (2xpi) 才能得到略低于您的数字的值,然后将其减去。就您而言,大约是 6.7 亿。

因此,它将 (2xpi) 乘以这个 9 位数的值 - 因此它实际上失去了数学库版本的 pi 的 9 位数的重要性。

我最终编写了一个小函数来测试发生了什么:

    private double reduceDown(double start)
    {

        decimal startDec = (decimal)start;
        decimal pi = decimal.Parse("3.1415926535897932384626433832795");
        decimal tau = pi * 2;
        int num = (int)(startDec / tau);
        decimal x = startDec - (num * tau);
        double retVal;
        double.TryParse(x.ToString(), out retVal);
        return retVal;
        //return start - (num * tau);
    }
Run Code Online (Sandbox Code Playgroud)

所有这一切都是使用十进制数据类型作为减少值的一种方式,而不会丢失 pi 的精度数字 - 它仍然返回双精度。当我通过修改您的代码来调用它时:

        var x = (double)4203708359;
        var c = Math.Cos(x);

        double y = reduceDown(x);
        double c2 = Math.Cos(y);

        MessageBox.Show(c.ToString() + Environment.NewLine + c2);
        return;
Run Code Online (Sandbox Code Playgroud)

……果然,第二个是准确的。

所以我的建议是 - 如果你真的需要那么高的弧度,并且你真的需要精度?执行类似上面的函数的操作,并以不丢失精度的方式减少最终的数字。