我有以下代码:
float f = 0.3f;
double d1 = System.Convert.ToDouble(f);
double d2 = System.Convert.ToDouble(f.ToString());
结果相当于:
d1 = 0.30000001192092896;
d2 = 0.3;
我很想知道为什么会这样?
答案 0 :(得分:23)
它不是精度损失.3不是representable in floating point。当系统转换为字符串时,它会舍入;如果你打印出足够多的有效数字,你会得到一些更有意义的东西。
更清楚地看到它
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 "
答案 1 :(得分:14)
你不会失去精确度;你从一个不太精确的表示(浮动,32位长)向上转换为更精确的表示(双倍,64位长)。你在更精确的表示(过去某一点)得到的只是垃圾。如果你将它从一个双精度数转换回浮点数,你将获得与之前完全相同的精度。
这里发生的是你为你的浮点分配了32位。然后你向上转换为双倍,再添加32位代表你的数字(总共64位)。这些新位是最不重要的(小数点右边最远),并且与实际值无关,因为它们之前是不确定的。结果,这些新位具有它们在您进行上转时碰巧拥有的任何值。他们和以前一样不确定 - 换句话说,垃圾。
当你从一个double向下转换为float时,它会丢掉那些最不重要的位,留下0.300000(7位数的精度)。
从字符串转换为float的机制不同;编译器需要分析字符串'0.3f'的语义含义,并弄清楚它与浮点值的关系。它不能像浮点/双转换那样进行位移 - 因此,你期望的值。
有关浮点数如何工作的更多信息,您可能有兴趣查看有关IEEE 754-1985标准的this维基百科文章(其中有一些方便的图片和对事物机制的良好解释),和this wiki关于2008年标准更新的文章。
修改强>
首先,正如@phoog在下面指出的那样,从浮点数向上转换为double并不像在保留记录数字的空间中添加另外32位一样简单。实际上,你将为指数增加3位(总共11位),并为该分数增加29位(总共52位)。添加符号位,你就得到了总共64位的双精度数。
此外,建议在这些最不重要的位置存在“垃圾位”,这是一个粗略的概括,并且可能不适合C#。下面的一些解释和一些测试告诉我,这对于C#/ .NET来说是确定性的,可能是转换中某些特定机制的结果,而不是为了额外的精度而保留内存。
回到过去,当您的代码编译成机器语言二进制文件时,编译器(至少是C和C ++编译器)在保留时不会添加任何CPU指令来“清除”或初始化内存中的值变量的空间。因此,除非程序员将变量显式初始化为某个值,否则为该位置保留的位值将保留它们在保留该内存之前所具有的任何值。
在.NET中,您的C#或其他.NET语言编译成中间语言(CIL,通用中间语言),然后由CLR进行即时编译以作为本机代码执行。可能有也可能没有C#编译器或JIT编译器添加的变量初始化步骤;我不确定。
这就是我所知道的:
double d1 = System.Convert.ToDouble(f);
结果:d1 : 0.300000011920929
double d2 = (double)f;
结果投射,我会得到相同的结果:d2 : 0.300000011920929
我们三个人得到相同的值,看起来upcast值是确定性的(实际上不是垃圾位),表明.NET在我们所有的机器上以相同的方式做某事 。仍然可以说,附加数字不比之前更精确或更不精确,因为0.3f并不完全等于0.3 - 它等于0.3,精确度高达7位。除了前七位之外,我们对附加数字的值一无所知。
答案 2 :(得分:3)
在这种情况下我使用十进制转换来获得正确的结果和其他情况
pbcopy