Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/294.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 在编译64位时,是什么导致FP精度的显著损失?_C#_Visual Studio 2013_Floating Point - Fatal编程技术网

C# 在编译64位时,是什么导致FP精度的显著损失?

C# 在编译64位时,是什么导致FP精度的显著损失?,c#,visual-studio-2013,floating-point,C#,Visual Studio 2013,Floating Point,平台:C#使用Visual Studio 2013 我有一个运行在64位Haswell CPU上的Windows应用程序,它在启用“首选32位”的情况下正常工作。我决定通过取消选择'prefer32-bit'升级到'prefer64-bit',应用程序的算术突然变为不正确的值我丢失了29位算术精度(这是我对双精度浮点尾数和单精度浮点尾数大小差异的估计)。这里的算术精度差异很大 C#代码…测试用例: using System; class lngfltdbl { static void M

平台:C#使用Visual Studio 2013

我有一个运行在64位Haswell CPU上的Windows应用程序,它在启用“首选32位”的情况下正常工作。我决定通过取消选择'prefer32-bit'升级到'prefer64-bit',应用程序的算术突然变为不正确的值我丢失了29位算术精度(这是我对双精度浮点尾数和单精度浮点尾数大小差异的估计)。这里的算术精度差异很大

C#代码…测试用例:

using System;
class lngfltdbl
{
    static void Main()
    {
        long   lng = 2026872;
        float  flt = 0.3F;
        double dbl = lng + flt;
        Console.WriteLine(dbl);
    }
}
预期结果(选择“首选32位”时可见):

获得的结果(在取消选择“首选32位”时可见):


请注意:过去我对隐式强制转换很熟悉,因为“首选32位”始终理解如何正确组合不同精度的值。

错误所在:

using System;
class lngfltdbl
{
    static void Main()
    {
        long   lng = 2026872;
        float  flt = 0.3F;
        double dbl = lng + flt;
        Console.WriteLine(dbl);
    }
}
在专家协助下,我们发现,取消选择“首选32位”生成的汇编代码确实使用单精度指令(cvtsi2ss;subss)进行计算,然后将结果转换为双精度(cvtss2sd:将标量双精度FP值转换为标量双精度FP值)最后,结果存储在双精度变量(movsd)中。这与检测到的错误的症状完全匹配,并解释了丢失29位算术精度的原因

我将此事上报给微软,并最终与JIT编译器团队中的某个人取得了联系。结果证明这是故意的行为,也就是说,如果使用隐式类型转换的双精度浮点算法,那么很可能您必须修改您的C#代码。到目前为止,我认为算术精度完全依赖于变量长度和任何显式/隐式转换(当然,在IEEE定义的浮点计算规则范围内)。此外,我认为选择将32位应用程序编译为64位不会改变应用程序的行为

我感谢微软给我发送了以下回复

您所看到的行为是您所提供的特定测试用例的预期行为。这里的关键是表达式

C#编译器生成IL来计算该表达式。它不考虑把这个表达式赋值给什么。表达式和赋值依赖于插入到表达式中的隐式转换。C#编译器有一些规则,指定在为表达式生成IL时如何将隐式转换添加到表达式中。在这种情况下,C#编译器添加了如下隐式转换:

这个表达式告诉JIT编译器它应该为单精度浮点加法操作生成代码。因此,考虑到JIT编译器提供的IL,64位目标生成的代码是完全合适的。它被(IL)告知要计算一个32位大小的浮点结果,正如您所观察到的那样

以下是此方法的IL:

然后问题就变成了为什么32位目标JIT会产生不同的(更精确的)结果

这里的答案是,较旧的32位使用较旧的x87风格的指令,我们一直说JIT编译器可以以更高的精度计算表达式的中间浮点值。32位JIT编译器实际上以更高的精度计算32位浮点表达式。之所以这样做,是因为这是使用旧的x87样式指令时可用指令的自然行为。我们这样做是因为使用x87风格的指令执行32位浮点运算会带来相当大的性能损失。我们还记录了,如果中间计算需要32位浮点结果,可以添加显式转换,并且需要JIT在看到显式转换时将精度更改为32位浮点

对于您的情况,您需要在add指令的两个操作数中的任意一个操作数上添加一个显式转换为“double”,以便C#编译器生成添加两个64位浮点的IL

以下任一源表达式将计算您想要的结果:


我想指出的是,您的
lng+flt
表达式会带来麻烦-您不应该在没有显式转换的情况下混合类型(正如Microsoft的回答所建议的那样)。我认为这不值得用大写字母谨慎。请用问答的方式重新表述。你可以回答你自己的问题!这是一个非常有趣且相当奇怪的观察结果,我觉得这将对该网站做出巨大贡献。@GregRos我觉得问题应该是“编译器选项是否具有功能性效果?”以及答案“是的,很明显。事实上,对于没有函数效果的编译器来说,例外情况而不是规则。例如,GCC的
-mfpmath=387
[GCC中与问题中讨论的选项最接近的等效项]更改生成代码的语义”。如果这没有在C#中正确记录,它应该是一个针对C#文档的错误报告,如果是,这只是一个“duh”备注,不是一个很大的贡献。我正在按照建议进行更改,但无法添加从Microsoft JIT编译器团队收到的答案。(我不想一开始就把答案分开,因为我不想为此获得任何荣誉)
dbl == 2026872.25
(ERROR!  CORRECT to 7 DECIMAL PLACES ONLY!)
lng + flt
((float)lng + flt)
.method private hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       26 (0x1a)
  .maxstack  2
  .locals init (int64 V_0,
           float32 V_1,
           float64 V_2)
  IL_0000:  ldc.i4     0x1eed78
  IL_0005:  conv.i8
  IL_0006:  stloc.0
  IL_0007:  ldc.r4     0.30000001
  IL_000c:  stloc.1
  IL_000d:  ldloc.0
  IL_000e:  conv.r4    ;; Force the conversion of ‘lng’ into a 32-bit float ‘r4’
  IL_000f:  ldloc.1
  IL_0010:  add
  IL_0011:  conv.r8
  IL_0012:  stloc.2
  IL_0013:  ldloc.2
  IL_0014:  call void [mscorlib]System.Console::WriteLine(float64)
  IL_0019:  ret
} // end of method lngfltdbl::Main
((double)lng + flt)
(lng + (double)flt)