Eug*_*e S 6 .net c# expression expression-trees
以下代码:
double c1 = 182273d;
double c2 = 0.888d;
Expression c1e = Expression.Constant(c1, typeof(double));
Expression c2e = Expression.Constant(c2, typeof(double));
Expression<Func<double, double>> sinee = a => Math.Sin(a);
Expression sine = ((MethodCallExpression)sinee.Body).Update(null, new[] { c1e });
Expression sum = Expression.Add(sine, c2e);
Func<double> f = Expression.Lambda<Func<double>>(sum).Compile();
double r = f();
double rr = Math.Sin(c1) + c2;
Console.WriteLine(r.ToString("R"));
Console.WriteLine(rr.ToString("R"));
Run Code Online (Sandbox Code Playgroud)
将输出:
0.082907514933846488
0.082907514933846516
Run Code Online (Sandbox Code Playgroud)
为什么r和rr不同?
更新:
发现如果选择"x86"平台目标或使用"任何CPU"检查"首选32位",则重现此项.在64x模式下正常工作.
我不是这方面的专家,但我会对此发表看法.
首先,只有在使用调试标志进行编译时才会出现问题(在发布模式下它不会出现),并且实际上只有在运行为x86时才出现.
如果我们反编译表达式编译的方法,我们将看到这个(在调试和发布中):
IL_0000: ldc.r8 182273 // push first value
IL_0009: call float64 [mscorlib]System.Math::Sin(float64) // call Math.Sin()
IL_000e: ldc.r8 0.888 // push second value
IL_0017: add // add
IL_0018: ret
Run Code Online (Sandbox Code Playgroud)
但是,如果我们查看在调试模式下编译的类似方法的IL代码,我们将看到:
.locals init (
[0] float64 V_0
)
IL_0001: ldc.r8 182273
IL_000a: call float64 [mscorlib]System.Math::Sin(float64)
IL_000f: ldc.r8 0.888
IL_0018: add
IL_0019: stloc.0 // save to local
IL_001a: br.s IL_001c // basically nop
IL_001c: ldloc.0 // V_0 // pop from local to stack
IL_001d: ret // return
Run Code Online (Sandbox Code Playgroud)
您会看到编译器将(不必要的)结果保存并加载到本地变量(可能用于调试目的).现在我不确定,但据我所知,在x86架构上,双值可能存储在80位 CPU寄存器中(引自此处):
默认情况下,在x86体系结构的代码中,编译器使用协处理器的80位寄存器来保存浮点计算的中间结果.这会提高程序速度并减少程序大小.但是,由于计算涉及在内存中表示的浮点数据类型少于80位,因此通过冗长的计算可以产生精度为80位的额外位减去较小浮点类型中的位数.结果不一致.
所以我的猜测是这个本地存储和从本地加载导致从64位转换到80位(因为寄存器)和返回,这会导致你观察到的行为.
另一种解释可能是JIT在调试和释放模式之间表现不同(可能仍然与将中间计算结果存储在80位寄存器中有关).
希望有些知道更多的人可以确认我是否正确.
更新以回应评论.反编译表达式的一种方法是创建动态程序集,将表达式编译到那里的方法,保存到磁盘,然后查看任何反编译器(我使用JetBrains DotPeek).例:
var asm = AppDomain.CurrentDomain.DefineDynamicAssembly(
new AssemblyName("dynamic_asm"),
AssemblyBuilderAccess.Save);
var module = asm.DefineDynamicModule("dynamic_mod", "dynamic_asm.dll");
var type = module.DefineType("DynamicType");
var method = type.DefineMethod(
"DynamicMethod", MethodAttributes.Public | MethodAttributes.Static);
Expression.Lambda<Func<double>>(sum).CompileToMethod(method);
type.CreateType();
asm.Save("dynamic_asm.dll");
Run Code Online (Sandbox Code Playgroud)