在x86调试和发布版本之间,双精度值发生1位变化

时间:2018-10-16 10:55:59

标签: .net vb.net roslyn

在一些数字代码中,我注意到为x86编译或使用AnyCPU +“首选32位”编译时的调试和发布版本产生了不同的结果。 我已经将代码分解成几乎可以重现该问题的最低限度的代码。 事实证明,在其中一个计算步骤中只有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中,Debug版本给出4498558851738655340,而Release版本给出4498558851738655341(请注意最后一位)。

我尝试在调试版本中手动启用优化功能(以及类似的DEBUG常量和pdb生成),但结果保持不变。仅将完整的构建类型更改为Release才能更改结果。

再进一步,我尝试比较Telerik的JustDecompile给出的IL:

调试版本:

.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 +首选32位选项)中。

编辑: 根据Tomers的评论,this question对待一个类似的想法,同时更加关注Debug / Release内部版本的差异。我仍然觉得有些奇怪,尽管当运行相同的代码时,不同的体系结构应该给出不同的结果。这是设计使然吗?我该如何信任自己计算出的值?

0 个答案:

没有答案