Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/330.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 使用Linq.表达式的算术计算在32位和64位上产生不同的结果_C#_Linq_Double_Expression_Precision - Fatal编程技术网

C# 使用Linq.表达式的算术计算在32位和64位上产生不同的结果

C# 使用Linq.表达式的算术计算在32位和64位上产生不同的结果,c#,linq,double,expression,precision,C#,Linq,Double,Expression,Precision,我观察到关于以下代码结果的一些奇怪行为: namespace Test { class Program { private static readonly MethodInfo Tan = typeof(Math).GetMethod("Tan", new[] { typeof(double) }); private static readonly MethodInfo Log = typeof(Math).GetMethod("Log", new[] { typeof(do

我观察到关于以下代码结果的一些奇怪行为:

namespace Test {
  class Program {
    private static readonly MethodInfo Tan = typeof(Math).GetMethod("Tan", new[] { typeof(double) });
    private static readonly MethodInfo Log = typeof(Math).GetMethod("Log", new[] { typeof(double) });

    static void Main(string[] args) {
    var c1 = 9.97601998143507984195821336470544338226318359375d;
    var c2 = -0.11209109500765944422706610339446342550218105316162109375d;

    var result1 = Math.Pow(Math.Tan(Math.Log(c1) / Math.Tan(c2)), 2);

    var p1 = Expression.Parameter(typeof(double));
    var p2 = Expression.Parameter(typeof(double));
    var expr = Expression.Power(Expression.Call(Tan, Expression.Divide(Expression.Call(Log, p1), Expression.Call(Tan, p2))), Expression.Constant(2d));
    var lambda = Expression.Lambda<Func<double, double, double>>(expr, p1, p2);
    var result2 = lambda.Compile()(c1, c2);

    var s1 = DoubleConverter.ToExactString(result1);
    var s2 = DoubleConverter.ToExactString(result2);

    Console.WriteLine("Result1: {0}", s1);
    Console.WriteLine("Result2: {0}", s2);
  }
}
但在为x86或任何Cpu编译时,结果会有所不同:

Result1: 4888.95508254035303252749145030975341796875
Result2: 4888.955082542781383381225168704986572265625
为什么
result1
保持不变,而
result2
则取决于目标体系结构?有没有办法使
result1
result2
在同一架构上保持不变

DoubleConverter
类取自。在你告诉我使用十进制之前,我不需要更高的精度,我只需要结果保持一致。目标框架是.NET4.5.2,测试项目是在调试模式下构建的。我正在Windows 10上使用Visual Studio 2015 Update 1 RC

谢谢

编辑

根据用户的建议,我尝试进一步简化示例:

  var c1 = 9.97601998143507984195821336470544338226318359375d;
  var c2 = -0.11209109500765944422706610339446342550218105316162109375d;
  var result1 = Math.Log(c1) / Math.Tan(c2);
  var p1 = Expression.Parameter(typeof(double));
  var p2 = Expression.Parameter(typeof(double));
  var expr = Expression.Divide(Expression.Call(Log, p1), Expression.Call(Tan, p2));
  var lambda = Expression.Lambda<Func<double, double, double>>(expr, p1, p2);
  var result2 = lambda.Compile()(c1, c2);
x64,调试:

Result1: -20.43465311535924655572671326808631420135498046875
Result2: -20.43465311535924655572671326808631420135498046875
x86或任意CPU,版本:

Result1: -20.434653115359243003013034467585384845733642578125
Result2: -20.434653115359243003013034467585384845733642578125
Result1: -20.43465311535924655572671326808631420135498046875
Result2: -20.43465311535924655572671326808631420135498046875
x64,发布:

Result1: -20.434653115359243003013034467585384845733642578125
Result2: -20.434653115359243003013034467585384845733642578125
Result1: -20.43465311535924655572671326808631420135498046875
Result2: -20.43465311535924655572671326808631420135498046875

关键是调试、发布、x86和x64的结果各不相同,公式越复杂,越有可能导致更大的偏差。

这是ECMA-335I.12.1.3浮点数据类型处理所允许的:

[…]浮点数(静态、数组元素和类字段)的存储位置大小固定。支持的存储大小为
float32
float64
。在其他任何地方(在计算堆栈上,作为参数、返回类型和局部变量),浮点数都使用内部浮点数类型表示。在每个这样的实例中,变量或表达式的标称类型为
float32
float64
,但其值可以在内部用额外的范围和/或精度表示。[……]

正如@harold对您的问题的评论,这允许在x86模式下使用80位FPU寄存器。这就是启用优化时发生的情况,这意味着对于用户代码,当您在发布模式下构建并且不进行调试时,但对于编译的表达式,总是这样

为了确保获得一致的舍入,需要将中间结果存储在字段或数组中。这意味着为了可靠地获得非
表达式
版本的结果,您需要将其编写为:

var tmp = new double[2];
tmp[0] = Math.Log(c1);
tmp[1] = Math.Tan(c2);
tmp[0] /= tmp[1];
tmp[0] = Math.Tan(tmp[0]);
tmp[0] = Math.Pow(tmp[0], 2);
然后您可以安全地将
tmp[0]
赋值给局部变量

是的,很难看


对于
表达式
版本,您需要的实际语法更糟糕,我不会写出来。它包括允许顺序执行多个不相关的子表达式,分配给数组元素或字段,以及访问这些数组元素或字段。

我无法在VS2012(Windows 7 SP1)上重现此问题。这两种架构都产生了相同的结果。我在Windows7,VS2015 x86/任何CPU上看到了与OP相同的结果。在Windows7,VS2015上重现。结果取决于附加的配置和调试器。在发布模式下,使用调试器(F5)和不使用调试器(Ctrl+F5)运行时,结果不同。32位JIT使用x87指令和80位临时结果,64位JIT不使用。但对于x87指令,如果它将临时内存保存到内存中,它将再次被截断为64位。因此,有时会有所不同,但不一定,这取决于确切的codegen。我认为Eric Lippert对以下问题的反应:“C#编译器、jitter和运行时都有广泛的用途,可以在任何时候、一时兴起的情况下为您提供比规范要求的更精确的结果——它们不需要一致地选择这样做,事实上也不需要。”当JIT升级(如果有)以优化内存访问时(可耻的是,现在它几乎从来没有这样做过)这将导致中断。或者,数组访问是否会根据规范强制精确?我认为对float/double进行冗余转换也会起作用。@usr规范,我引用的部分,说字段和数组元素可能没有额外的精度。不需要强制转换来删除多余的精度。好的,这样就可以了。也可以使用
结构{float F;}
和一个通过该结构的助手方法。谢谢!非常有用。