将浮点数转换为双精度会失去精度,但不是通过 ToString 进行的

本文关键字:ToString 转换 浮点数 双精度 精度 失去 | 更新日期: 2023-09-27 17:56:13

我有以下代码:

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

结果等效于:

d1 = 0.30000001192092896;
d2 = 0.3;

我很想知道这是为什么?

将浮点数转换为双精度会失去精度,但不是通过 ToString 进行的

它不是精度损失 .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 位)。这些新位是最不重要的(小数点右边最远的位),并且与实际值无关,因为它们以前是不确定的。 因此,这些新位具有您进行向上转换时碰巧具有的任何值。它们和以前一样不确定 - 换句话说,垃圾。

当您从双精度下降到浮点数时,它会删除那些最低有效位,留下 0.300000(7 位精度)。

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

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

编辑:

首先,正如下面@phoog指出的,从浮点数向上转换为双精度并不像在保留的空间中添加另外 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
  • 如果我使用结果:d2 : 0.300000011920929进行投射double d2 = (double)f;我会得到相同的结果

由于我们三个人获得相同的值,看起来向上转换的值是确定性的(而不是实际上是垃圾位),这表明 .NET 在我们的所有机器上都以相同的方式执行操作。可以说,额外的数字并不比以前更精确或更不精确,因为 0.3f 并不完全等于 0.3 - 它等于 0.3,精度高达 7 位。我们对前七个数字以外的其他数字的值一无所知。

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

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