As already mentioned, this is due to the difference between the Debug and Release modes on x86. It appeared in your code in Debug mode, because the compiled lambda expression is always JIT compiled in Release mode.
The difference is not caused by the C # compiler. Consider the following version of your code:
using System; using System.Runtime.CompilerServices; static class Program { static void Main() => Console.WriteLine(Compute().ToString("R")); [MethodImpl(MethodImplOptions.NoInlining)] static double Compute() => Math.Sin(182273d) + 0.888d; }
The output is 0.082907514933846516
in debug mode and 0.082907514933846488
in release mode, but IL is the same for both:
.class private abstract sealed auto ansi beforefieldinit Program extends [mscorlib]System.Object { .method private hidebysig static void Main() cil managed { .entrypoint .maxstack 2 .locals init ([0] float64 V_0) IL_0000: call float64 Program::Compute() IL_0005: stloc.0
The difference is the generated machine code. Disassembling Compute
for debug mode:
012E04B2 in al,dx 012E04B3 push edi 012E04B4 push esi 012E04B5 push ebx 012E04B6 sub esp,34h 012E04B9 xor ebx,ebx 012E04BB mov dword ptr [ebp-10h],ebx 012E04BE mov dword ptr [ebp-1Ch],ebx 012E04C1 cmp dword ptr ds:[1284288h],0 012E04C8 je 012E04CF 012E04CA call 71A96150 012E04CF fld qword ptr ds:[12E04F8h] 012E04D5 sub esp,8 012E04D8 fstp qword ptr [esp] 012E04DB call 71C87C80 012E04E0 fstp qword ptr [ebp-40h] 012E04E3 fld qword ptr [ebp-40h] 012E04E6 fadd qword ptr ds:[12E0500h] 012E04EC lea esp,[ebp-0Ch] 012E04EF pop ebx 012E04F0 pop esi 012E04F1 pop edi 012E04F2 pop ebp 012E04F3 ret
In Release mode:
00C204A0 push ebp 00C204A1 mov ebp,esp 00C204A3 fld dword ptr ds:[0C204B8h] 00C204A9 fsin 00C204AB fadd qword ptr ds:[0C204C0h] 00C204B1 pop ebp 00C204B2 ret
In addition to using a function call to calculate sin
instead of directly using fsin
, which does not seem to matter much, the main change is that Release mode saves the result of sin
in a floating mode, then Debug mode writes it and then reads it into memory ( fstp qword ptr [ebp-40h]
instructions fstp qword ptr [ebp-40h]
and fld qword ptr [ebp-40h]
). This means that it rounds the sin
result from 80-bit precision to 64-bit precision, which leads to different values.
Curiously, the result of the same code on .Net Core (x64) is another value: 0.082907514933846627
. Parsing for this case shows that it uses SSE instructions, not x87 (although the .Net Framework x64 does the same, so the difference will be in the function being called):
00007FFD5C180B80 sub rsp,28h 00007FFD5C180B84 movsd xmm0,mmword ptr [7FFD5C180BA0h] 00007FFD5C180B8C call 00007FFDBBEC1C30 00007FFD5C180B91 addsd xmm0,mmword ptr [7FFD5C180BA8h] 00007FFD5C180B99 add rsp,28h 00007FFD5C180B9D ret