.net 双精度值中的1位在x86调试和发布构建版本之间发生变化

pkbketx9  于 2023-01-14  发布在  .NET
关注(0)|答案(1)|浏览(81)

在一些数字代码中,我注意到在x86或AnyCPU +"Prefer 32-bit"下编译的调试和发布版本给出了不同的结果。我已经将我的代码分解到几乎是重现问题的最小值。结果是,在一个计算步骤中只有1位更改。
代码:

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Dim tau = 0.000001
    Dim a = 0.5
    Dim value2 = (2 * Math.PI * 0.000000001 * tau) ^ a * Math.Sin(a * (Math.PI / 2))
    RichTextBox1.Text = BitConverter.DoubleToInt64Bits(value2).ToString
End Sub

在Int64中,调试版本给出4498558851738655340,发布版本给出4498558851738655341(注意最后一位数字)。
我尝试在调试构建中手动启用优化(同样地,DEBUG常量和pdb生成),但结果保持不变。只有将完整构建类型更改为Release才能更改结果。
更进一步,我尝试比较IL,如Telerik的JustDecompile所给出的:
调试版本:

.method private instance void Button1_Click (
        object sender,
        class [mscorlib]System.EventArgs e
    ) cil managed 
{
    .locals init (
        [0] float64 V_0,
        [1] float64 V_1,
        [2] float64 V_2,
        [3] int64 V_3
    )

    IL_0000: nop
    IL_0001: ldc.r8 1E-06
    IL_000a: stloc.0
    IL_000b: ldc.r8 0.5
    IL_0014: stloc.1
    IL_0015: ldc.r8 6.2831853071795863E-09
    IL_001e: ldloc.0
    IL_001f: mul
    IL_0020: ldloc.1
    IL_0021: call float64 [mscorlib]System.Math::Pow(float64,  float64)
    IL_0026: ldloc.1
    IL_0027: ldc.r8 1.5707963267948966
    IL_0030: mul
    IL_0031: call float64 [mscorlib]System.Math::Sin(float64)
    IL_0036: mul
    IL_0037: stloc.2
    IL_0038: ldarg.0
    IL_0039: callvirt instance class [System.Windows.Forms]System.Windows.Forms.RichTextBox CETestGenerator.Form1::get_RichTextBox1()
    IL_003e: ldloc.2
    IL_003f: call int64 [mscorlib]System.BitConverter::DoubleToInt64Bits(float64)
    IL_0044: stloc.3
    IL_0045: ldloca.s V_3
    IL_0047: call instance string [mscorlib]System.Int64::ToString()
    IL_004c: callvirt instance void [System.Windows.Forms]System.Windows.Forms.RichTextBox::set_Text(string)
    IL_0051: nop
    IL_0052: ret
}

发布版本:

.method private instance void Button1_Click (
        object sender,
        class [mscorlib]System.EventArgs e
    ) cil managed 
{
    .locals init (
        [0] float64 V_0,
        [1] float64 V_1,
        [2] float64 V_2,
        [3] int64 V_3
    )

    IL_0000: ldc.r8 1E-06
    IL_0009: stloc.0
    IL_000a: ldc.r8 0.5
    IL_0013: stloc.1
    IL_0014: ldc.r8 6.2831853071795863E-09
    IL_001d: ldloc.0
    IL_001e: mul
    IL_001f: ldloc.1
    IL_0020: call float64 [mscorlib]System.Math::Pow(float64,  float64)
    IL_0025: ldloc.1
    IL_0026: ldc.r8 1.5707963267948966
    IL_002f: mul
    IL_0030: call float64 [mscorlib]System.Math::Sin(float64)
    IL_0035: mul
    IL_0036: stloc.2
    IL_0037: ldarg.0
    IL_0038: callvirt instance class [System.Windows.Forms]System.Windows.Forms.RichTextBox CETestGenerator.Form1::get_RichTextBox1()
    IL_003d: ldloc.2
    IL_003e: call int64 [mscorlib]System.BitConverter::DoubleToInt64Bits(float64)
    IL_0043: stloc.3
    IL_0044: ldloca.s V_3
    IL_0046: call instance string [mscorlib]System.Int64::ToString()
    IL_004b: callvirt instance void [System.Windows.Forms]System.Windows.Forms.RichTextBox::set_Text(string)
    IL_0050: ret
}

正如您所看到的,它们几乎是相同的,唯一的区别是两个额外的nop命令(它们不应该做任何事情?)
现在我的问题是,我是不是做错了什么,是编译器或者框架做了什么奇怪的事情,还是就是这样?我知道不是每个数字都可以用双精度表示。这就是为什么我比较Int64表示法。不过我的理解是,结果不应该在构建之间改变。
假设仅仅启用优化并不能改变它,那么Debug和Release之间可能导致这种情况的进一步区别是什么?
我正在为. NET Framework 4.5编译,正如我上面所说,错误只发生在x86构建(或AnyCPU + Prefer 32位选项)中。

    • 编辑:**根据Tomers的评论,this question处理了类似的想法,同时更关注调试/发布版本的差异。我仍然觉得有点奇怪,不同的架构在运行相同的代码时会给出不同的结果。这是设计好的吗?那么我如何信任我计算的值呢?
aiazj4mn

aiazj4mn1#

Windows 11上,我已经在Visual Studio 2019上使用.Net Framework 4.7.2和在Visual Studio 2022上使用.Net 6测试了您的代码(做了一些修改)。
使用DebugRelease版本显示的结果总是相同的。我看不出任何差异。
Visual Studio 2019始终返回以40结尾的值作为调试版本,Visual Studio 2022在为x64 cible编译时返回以41结尾的值。
Visual Studio 2022安装在我的PC上是一个64位的应用程序。
我使用了以下程序

Imports System

Module Program
    Sub Main(args As String())
        Dim tau As Double = 0.000001
        Dim a As Double = 0.5
        Dim n1 = 2 * Math.PI * 0.000000001 * tau
        Dim n2 = Math.Sin(a * (Math.PI / 2))
        Dim n3 = n1 ^ a
        Dim n4 = n3 * n2
        Dim x2 = Math.Sin(Math.PI * a / 2)
        Dim x3 = a * Math.PI / 2
        Dim x4 = Math.Sin(x3)
        Dim x5 = Math.Sin(0.78539816339744828)

        Dim value2 As Double = (2 * Math.PI * 0.000000001 * tau) ^ a * Math.Sin(a * (Math.PI / 2))
        Dim s As String = BitConverter.DoubleToInt64Bits(value2).ToString

        Console.WriteLine(">> 64 bits signed integer : " & s)
        Console.WriteLine(">> n1 : " & n1)
        Console.WriteLine(">> n2 : " & n2)
        Console.WriteLine(">> n3 : " & n3)
        Console.WriteLine(">> n4 : " & n4)
        Console.WriteLine(">> 64 bits n4: " & BitConverter.DoubleToInt64Bits(n4).ToString)
        Console.WriteLine(">> x2 : " & x2)
        Console.WriteLine(">> x3 : " & x3)
        Console.WriteLine(">> x4 : " & x4)
        Console.WriteLine(">> x5 : " & x5)
    End Sub
End Module

x86生成正在显示以下结果

>> 64 bits signed integer : 4498558851738655340
>> n1 : 6,283185307179586E-15
>> n2 : 0,7071067811865475
>> n3 : 7,926654595212022E-08
>> n4 : 5,604991216397928E-08
>> 64 bits n4: 4498558851738655340
>> x2 : 0,7071067811865475
>> x3 : 0,7853981633974483
>> x4 : 0,7071067811865475
>> x5 : 0,7071067811865475

x64生成正在显示以下结果

>> 64 bits signed integer : 4498558851738655341
>> n1 : 6,283185307179586E-15
>> n2 : 0,7071067811865476
>> n3 : 7,926654595212022E-08
>> n4 : 5,6049912163979286E-08
>> 64 bits n4: 4498558851738655341
>> x2 : 0,7071067811865476
>> x3 : 0,7853981633974483
>> x4 : 0,7071067811865476
>> x5 : 0,7071067811865476

x86x64构建版本之间的差异是由于Math.Sin()功能(参见变量x4和x5)!

相关问题