提问人:Jens 提问时间:10/16/2018 最后编辑:Jens 更新时间:1/11/2023 访问量:205
x86 调试和发布版本之间的双精度值更改为 1 位
1 bit in double value changes between x86 Debug and Release build
问:
在一些数字代码中,我注意到针对 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 版本和 Release 版本给出(注意最后一位数字)。4498558851738655340
4498558851738655341
我尝试在调试版本中手动启用优化(以及 DEBUG 常量和 pdb 生成),但结果保持不变。仅将完整生成类型更改为“发布”才会更改结果。
更进一步,我尝试比较 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 的评论,这个问题处理了类似的想法,同时更侧重于调试/发布构建的差异。我仍然觉得有点奇怪,当运行相同的代码时,不同的架构应该给出不同的结果。这是设计使然吗?我怎样才能相信我计算出的值呢?
答:
上,我已经测试了您的代码(进行了一些调整)使用和使用。Windows 11
Visual Studio 2019
.Net Framework 4.7.2
Visual Studio 2022
.Net 6
使用 或 版本显示的结果始终相同。我看不出有什么区别。Debug
Release
Visual Studio 2019
始终返回一个以 40 结尾的值作为调试版本,并在为 cible 编译时返回一个以 41 结尾的值。Visual Studio 2022
x64
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
和 build 之间的区别是由于函数(参见变量 x4 和 x5)!x86
x64
Math.Sin()
评论