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

Jos*_*shG 22 c# string floating-point precision double

我有以下代码:

float f = 0.3f;
double d1 = System.Convert.ToDouble(f);
double d2 = System.Convert.ToDouble(f.ToString());
Run Code Online (Sandbox Code Playgroud)

结果相当于:

d1 = 0.30000001192092896;
d2 = 0.3;
Run Code Online (Sandbox Code Playgroud)

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

rer*_*run 23

它不是精度损失.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);
Run Code Online (Sandbox Code Playgroud)

产量

"d1 : 0.300000011920929 ; d2 : 0.300000012 "
Run Code Online (Sandbox Code Playgroud)

  • +1!有两个问题...转换为字符串时,浮点数四舍五入为多少位(多少位)?更重要的是,为什么?如果有人使用浮点数并分配了一个值,但由于浮点限制而没有存储确切的值,那么 ToString 到底为什么会决定为您舍入呢?更糟糕的是,因为调试器输出当然会执行相同的操作,因此 (float)0.3 之类的内容仍然在调试输出中显示 0.3,并且您永远不会意识到您正在失去该精度。那太愚蠢了。 (2认同)

Joe*_*Joe 14

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

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

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

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

有关如何浮点数的工作更多的信息,你可能有兴趣在检查出这个在IEEE 754-1985标准维基百科的文章(其中有一些方便的图片和事物的力学很好的解释),而这个 wiki文章的更新符合2008年的标准.

编辑:

首先,正如@phoog在下面指出的那样,从浮点数向上转换为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;Result进行转换,我会得到相同的结果:d2 : 0.300000011920929

随着我们三个人得到相同的价值观,它看起来像上溯造型值是确定的(而不是实际的垃圾位),这表明.NET做的东西在我们所有的机器一样.仍然可以说,附加数字不比之前更精确或更不精确,因为0.3f并不完全等于0.3 - 它等于0.3,精确度高达7位.除了前七位之外,我们对附加数字的值一无所知.

  • 关于最低有效位的那一点是之前存储器中可能存在的任何垃圾是不正确的(至少不是在C#中).首先,浮点数不仅仅是删除32位的双精度数; 用于指定指数的位数是不同的,指数偏差也是如此.其次,如果_were_为真,那么就不可能从浮点数到双数并且一致地往返. (3认同)