作为旁注,.NET 计算 sin/cos/tan 的方式发生了变化。它发生在 2016 年 6 月 2 日,this commit 在 .NET Core 上。正如作者在floatdouble.cpp中所写:
AMD64 Windows 上的 Sin、Cos 和 Tan 之前在 vm\amd64\JitHelpers_Fast.asm 中实现
通过调用 x87 浮点代码(fsin、fcos、fptan),因为 CRT 助手太慢了。这
不再是这种情况,所有平台都使用 CRT 调用。
请注意,CRT 是 C 语言运行时。这解释了为什么较新版本的 .NET Core在同一平台上使用时会提供与 C++ 相同的结果。它们使用的 CRT 与其他 C++ 程序使用的相同。
“旧”代码的最新版本供感兴趣的人使用:1 和 2。
尚不清楚 .NET Framework 是否/何时继承了这些更改。从一些测试看来,
.NET Core >= 2.0(未测试过以前的版本):Math.Sin(6.2831853071795856E+45) == 0.824816390616968
.NET Framework 4.8(32 位和 64 位):Math.Sin(6.2831853071795856E+45) == 6.28318530717959E+45
所以它没有继承它们。
有趣的附录
做了一些检查,Math.Sin(6.2831853071795856E+45) == 6.28318530717959E+45 是x87 程序集的fsin 操作码的“官方”答案,所以它是英特尔(大约 1980 年)关于罪过多少的“官方”答案6.2831853071795856E+45,所以如果你使用英特尔,你必须相信Math.Sin(6.2831853071795856E+45) == 6.28318530717959E+45,否则你就是叛徒! ???
如果你想仔细检查:
public static class TrigAsm
{
[DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)]
private static extern IntPtr VirtualAlloc(IntPtr lpAddress, IntPtr dwSize, uint flAllocationType, uint flProtect);
[DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool VirtualProtect(IntPtr lpAddress, IntPtr dwSize, uint flAllocationType, out uint lpflOldProtect);
[DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool VirtualFree(IntPtr lpAddress, IntPtr dwSize, uint dwFreeType);
private const uint PAGE_READWRITE = 0x04;
private const uint PAGE_EXECUTE = 0x10;
private const uint MEM_COMMIT = 0x1000;
private const uint MEM_RELEASE = 0x8000;
[SuppressUnmanagedCodeSecurity]
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate double Double2Double(double d);
public static readonly Double2Double Sin;
static TrigAsm()
{
// Opcoes generated with https://defuse.ca/online-x86-assembler.htm
byte[] body = Environment.Is64BitProcess ?
new byte[]
{
0xF2, 0x0F, 0x11, 0x44, 0x24, 0x08, // movsd QWORD PTR [rsp+0x8],xmm0
0xDD, 0x44, 0x24, 0x08, // fld QWORD PTR [rsp+0x8]
0xD9, 0xFE, // fsin
0xDD, 0x5C, 0x24, 0x08, // fstp QWORD PTR [rsp+0x8]
0xF2, 0x0F, 0x10, 0x44, 0x24, 0x08, // movsd xmm0,QWORD PTR [rsp+0x8]
0xC3, // ret
} :
new byte[]
{
0xDD, 0x44, 0x24, 0x04, // fld QWORD PTR [esp+0x4]
0xD9, 0xFE, // fsin
0xC2, 0x08, 0x00, // ret 0x8
};
IntPtr buf = IntPtr.Zero;
try
{
// We VirtualAlloc body.Length bytes, with R/W access
// Note that from what I've read, MEM_RESERVE is useless
// if the first parameter is IntPtr.Zero
buf = VirtualAlloc(IntPtr.Zero, (IntPtr)body.Length, MEM_COMMIT, PAGE_READWRITE);
if (buf == IntPtr.Zero)
{
throw new Win32Exception();
}
// Copy our instructions in the buf
Marshal.Copy(body, 0, buf, body.Length);
// Change the access of the allocated memory from R/W to Execute
uint oldProtection;
bool result = VirtualProtect(buf, (IntPtr)body.Length, PAGE_EXECUTE, out oldProtection);
if (!result)
{
throw new Win32Exception();
}
// Create a delegate to the "function"
Sin = (Double2Double)Marshal.GetDelegateForFunctionPointer(buf, typeof(Double2Double));
buf = IntPtr.Zero;
}
finally
{
// There was an error!
if (buf != IntPtr.Zero)
{
// Free the allocated memory
bool result = VirtualFree(buf, IntPtr.Zero, MEM_RELEASE);
if (!result)
{
throw new Win32Exception();
}
}
}
}
}
在 .NET 中不能有内联汇编(Visual Studio 的内联汇编只有 32 位)。我解决了将汇编操作码放在一块内存中并将内存标记为可执行文件的问题。
后记:请注意,没有人再使用 x87 指令集,因为它很慢。