将float转换为double丢失精度但不通过ToString

我有以下代码:

float f = 0.3f; double d1 = System.Convert.ToDouble(f); double d2 = System.Convert.ToDouble(f.ToString()); 

结果相当于:

 d1 = 0.30000001192092896; d2 = 0.3; 

我很想知道为什么会这样?

它不是精度损失.3 在浮点数上是不可表示的 。 当系统转换为字符串时,它会舍入; 如果你打印出足够多的有效数字,你会得到更有意义的东西。

要更清楚地看到它

 float f = 0.3f; double d1 = System.Convert.ToDouble(f); double d2 = System.Convert.ToDouble(f.ToString("G20")); string s = string.Format("d1 : {0} ; d2 : {1} ", d1, d2); 

产量

 "d1 : 0.300000011920929 ; d2 : 0.300000012 " 

你不会失去精确度; 你从一个不太精确的表示(浮动,32位长)向上转换为更精确的表示(双倍,64位长)。 你在更精确的表示(过去某一点)得到的只是垃圾。 如果你将它从一个双精度数转换回浮点数,你将获得与之前完全相同的精度。

这里发生的是你为你的浮点分配了32位。 然后你向上转换为一个双倍,再添加32位来表示你的数字(总共64位)。 这些新位是最不重要的(小数点右边最远),并且与实际值无关,因为它们之前是不确定的。 结果,这些新位具有您在进行上传时碰巧拥有的任何值。 他们和以前一样不确定 – 换句话说就是垃圾。

当你从一个double向下转换为float时,它会丢掉那些最不重要的位,让你得到0.300000(7位数的精度)。

从字符串转换为浮点数的机制是不同的; 编译器需要分析字符串’0.3f’的语义含义,并弄清楚它与浮点值的关系。 它不能像浮点/双转换那样进行位移 – 因此,你期望的值。

有关浮点数如何工作的更多信息,您可能有兴趣查看关于IEEE 754-1985标准的维基百科文章(它有一些方便的图片和对事物机制的良好解释),以及关于更新的这篇 wiki文章符合2008年的标准。

编辑:

首先,正如@phoog在下面指出的那样,从float到double的向上转换并不像在保留记录数字的空间中添加另外32位那么简单。 实际上,你将为指数增加3位(总共11位),并为该分数增加29位(总共52位)。 添加符号位,你就得到了总共64位的双精度数。

另外,建议在那些最不重要的位置存在“垃圾位”,这是一个粗略的概括,并且可能不适合C#。 一些解释,以及下面的一些测试告诉我,这对于C#/ .NET来说是确定性的,并且可能是转换中某些特定机制的结果,而不是为了额外的精度而保留内存。

回到过去,当您的代码编译成机器语言二进制文件时,编译器(至少是C和C ++编译器)不会添加任何CPU指令来“清除”或在为内存保留空间时初始化内存中的值变量。 因此,除非程序员明确地将变量初始化为某个值,否则为该位置保留的位的值将保留它们在保留该内存之前所具有的任何值。

在.NET版本中,您的C#或其他.NET语言编译成中间语言(CIL,通用中间语言),然后由CLR进行即时编译以作为本机代码执行。 可能有也可能没有C#编译器或JIT编译器添加的变量初始化步骤; 我不确定。

这就是我所知道的:

  • 我通过将浮子投射到三个不同的双打来测试这个。 每个结果都具有完全相同的值。
  • 该值与上面的@rerun值完全相同: double d1 = System.Convert.ToDouble(f); 结果: d1 : 0.300000011920929
  • 如果我使用double d2 = (double)f;投射,我会得到相同的结果double d2 = (double)f; 结果: d2 : 0.300000011920929

当我们三个人得到相同的值时,看起来upcast值是确定性的(而不是实际上是垃圾位),表明.NET在我们所有的机器上都采用相同的方式。 仍然可以说,附加数字不比之前更精确或更不精确,因为0.3f并不完全等于0.3 – 它等于0.3,精确度高达7位。 除了前七位之外,我们对附加数字的值一无所知。

在这种情况下,我使用十进制转换为正确的结果和相同的其他情况

 float ff = 99.95f; double dd = (double)(decimal)ff;