C# 发布和调试模式给出不同的结果,除非我打印一个值

C# 发布和调试模式给出不同的结果,除非我打印一个值,c#,floating-point,precision,C#,Floating Point,Precision,我有发布和调试代码,可以给出截然不同的结果。因为我无法调试发行版代码,所以我一直在将值打印到一个文件中,以比较这两种模式。我已将问题局限于几行代码 var yFloat = topArcDetails.ArcPoints.GetInterpolatedYAt(point.X); var tvdOnTopHorizon = (double) yFloat; var yPointDouble = (double) point.Y; deltaTvdTop = yPointDouble - tvdOn

我有发布和调试代码,可以给出截然不同的结果。因为我无法调试发行版代码,所以我一直在将值打印到一个文件中,以比较这两种模式。我已将问题局限于几行代码

var yFloat = topArcDetails.ArcPoints.GetInterpolatedYAt(point.X);
var tvdOnTopHorizon = (double) yFloat;
var yPointDouble = (double) point.Y;
deltaTvdTop = yPointDouble - tvdOnTopHorizon;
if (point.X == 2102.0 && point.Y > 1573.0 && point.Y < 1574.0)
{
    var message = String.Format("MD, TVD,         tvdOnTopHorizon, deltaTvdTop = {0}  {1}          {2}  {3}", point.X, point.Y, tvdOnTopHorizon, deltaTvdTop);
    //var message = String.Format("MD, TVD, yFloat, tvdOnTopHorizon, deltaTvdTop = {0}  {1}  {2}  {3}  {4}", point.X, point.Y, yFloat, tvdOnTopHorizon, deltaTvdTop);
    var oldOut = Console.Out;
    using (TextWriter w = File.AppendText("debugOutput.txt"))
    {
        Console.SetOut(w);
        Console.WriteLine(message);
    }
    Console.SetOut(oldOut);
}
第二条信息将提供:

MD, TVD,         tvdOnTopHorizon, deltaTvdTop = 2102  1573.135            1554.6184437573   18.5164439380169
MD, TVD, yFloat, tvdOnTopHorizon, deltaTvdTop = 2102  1573.135  1554.618  1554.61840820313  18.5164794921875
请注意,仅打印yFloat的值会导致最后两个数字的微小变化。在调试模式下,我看不到打印语句造成的差异。两个结果是:

MD, TVD, yFloat, tvdOnTopHorizon, deltaTvdTop = 2102  1573.135  1554.618  1554.61840820313  18.5164794921875
MD, TVD,         tvdOnTopHorizon, deltaTvdTop = 2102  1573.135            1554.61840820313  18.5164794921875
调试结果与打印yFloat的发布结果一致。我知道核心问题是代码混合了浮点和双精度,这是其他人的坏主意。虽然打印yFloat的值可以解决这个问题,但我认为这并不实用。这确实让我觉得我必须做些什么才能使发布版本与调试版本一致。

C语言规范(您应该在类似
C:\ProgramFiles(x86)\Microsoft Visual Studio 14.0\VC\Specifications\1033
的文件夹中有一份副本,或者您可以)在第4.1.6节浮点类型中说明:

浮点运算的执行精度可能高于运算的结果类型。例如,某些硬件体系结构支持“扩展”或“长双精度”浮点类型,其范围和精度比双精度类型更大,并使用此更高精度类型隐式执行所有浮点运算。只有在性能成本过高的情况下,这样的硬件体系结构才能以较低的精度执行浮点运算,并且C#允许对所有浮点运算使用更高精度的类型,而不是要求实现同时丧失性能和精度。除了提供更精确的结果,这很少有任何可测量的效果。然而,在形式为x×y/z的表达式中,其中乘法产生的结果在双范围之外,但是随后的除法将临时结果带回到双范围中,以更高范围格式计算表达式的事实可能导致产生有限结果而不是无穷大

(关键是我用粗体写的句子。)

从本质上说,这意味着C#编译器可以随意优化代码,只要精度更好而不是更差

在您的例子中,虽然
topArcDetails.ArcPoints.GetInterpolatedYAt(point.X)
返回一个
浮点值
,但结果用于计算一个
双精度值
。显然,在构建发布版本时,编译器将代码内联作为优化,绕过到
float
的转换。(在x86版本与x64版本模式下编译时,您可能还会发现输出的差异。)

您可以显式强制转换该值,即使这看起来是多余的

var yFloat = (float) topArcDetails.ArcPoints.GetInterpolatedYAt(point.X);
这将迫使编译器在使用它之前将结果转换为
浮点值


因此,总之,您必须在准确性和可重复性之间进行选择。如果您最关心的是获得最准确的结果(也许还有更好的性能),请允许编译器进行优化。如果您更关心跨平台获得相同的结果,则需要手动添加额外的强制转换,以将中间计算强制转换为固定的位数。

我相信您的问题已在中解释。然而,我有金徽章复制锤的权利,所以我不会投票作为复制品结束,因为我不是100%确定。我的猜测是,在调试模式下,计算存储在32位和64位内存位置。在释放模式下,计算存储在FPU上的80个寄存器中,导致不同的结果。我将检查它。我会注意到我已经消除了所有的打印,只是添加了var str=String.Format(“{0}”,yFloat);在我计算完yFloat之后。这个程序现在运行得很好,但不知怎么的,它似乎不是一个解决方案。马丁,我看了那篇文章,没有看到多少有意义的东西。如果你做
var yFloat=(float)topArcDetails.ArcPoints.GetInterpolatedYAt(point.X),你会得到什么(即强制转换)而不进行字符串转换?我没有尝试显式转换,因为方法GetInterpolatedYAt返回浮点。我可以在明天上班的时候试试。我想当你不了解.NET和C#编译器的内部结构时,这些陷阱就会悄悄地出现在你身上。我在精度和通用性方面遇到过类似的问题,只要我能管理它,我就更喜欢使用十进制,这样做是有意义的(奇怪的是Silverlight去掉了许多对十进制进行操作的BCL函数)。@Eniola关于
十进制的观点很好。不过,所有数字表示法都存在一些陷阱;例如
Decimal.MinValue+0.1M==Decimal.MinValue
为真。但对于二进制浮点类型,通常最好总是将值视为(接近)近似值,这通常是您所需要的。要么我没有遇到它,要么我没有注意到它。BCL团队有没有关于为什么会这样的消息?这是标准的一部分还是实施中的错误?在Mono或新的.NET内核上会得到相同的结果吗?@Eniola我认为这只是因为可以使用的位数有限,所以在极端情况下总是会失去精度。