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

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

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

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

我越来越

 c = -0.57977754519440394 

Windows的计算器给出了

 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

它似乎是一个内置function。 我没有在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

我想我可能知道答案。 我很确定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; 

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

所以我的建议是 – 如果你真的需要那么高的弧度,你真的需要准确度吗? 执行类似上面的function,并以不丢失精度数字的方式减少数字。

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

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

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

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