Math.Cos()的精度为大整数

时间:2017-07-10 16:12:06

标签: c# floating-point

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

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

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

我正在

c = -0.57977754519440394

视窗'计算器给出

c = -0.579777545198813380788467070278

PHP的cos(double)函数(内部只使用C标准库中的cos(double))给出了:

c = -0.57977754519881

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

c = -0.57977754519881342

以下是C#中Math.cos()的定义: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

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

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

3 个答案:

答案 0 :(得分:2)

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

问题是,该程序如何减少你的10位数字?实际上,它应该计算出需要乘以(2xpi)以获得刚好低于数字的值的次数,然后将其减去。在你的情况下,这大约是6.7亿。

所以它将这个9位数值乘以(2xpi) - 所以它实际上从数学库的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);
    }

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

        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;

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

所以我的建议是 - 如果你真的需要那么高的弧度,你真的需要精确度吗?做一些类似上面这个功能的事情,并以不丢失精度数字的方式减少你的数字。

答案 1 :(得分:2)

据推测,盐与每个密码一起存储。您可以使用PHP代码计算余弦,并将其与密码一起存储。然后,我还会添加密码版本号,并将所有旧密码默认为版本1.然后,在您的C#代码中,对于任何新密码,您实施新的哈希算法,并将这些密码哈希存储为密码版本2。任何版本1密码,要进行身份验证,您不必计算余弦,您只需使用与密码哈希和盐一起存储的密码。

PHP代码的程序员可能想要做一个聪明的胡椒版本。通过存储余弦,或胡椒以及盐和密码哈希,你基本上将胡椒变成盐2。因此,另一种无版本的方法是在C#散列代码中使用两个salt。对于新密码,您可以将第二个盐留空或以其他方式分配。对于旧密码,它将是余弦,但它已经计算好了。

答案 2 :(得分:1)

关于我的问题的这一部分:“为什么我的C#示例中的精度如此差”,coreclr开发人员在这里回答:https://github.com/dotnet/coreclr/issues/12737

简而言之,.NET Framework 4.6.2(x86和x64)和.NET Core(x86)似乎使用Intel的x87 FP单元(即fcosfsincos),但结果不准确而x64上的.NET Core(以及PHP,Visual Studio 2017和gcc)使用更准确,可能是基于SSE2的实现,可以提供正确的舍入结果。