如何在不添加额外数字的情况下将float转换为double

本文关键字:情况下 float 转换 double 数字 添加 | 更新日期: 2023-09-27 18:18:55

我遇到了一个有趣的问题:当我将float-99.9f转换为double变量时,该变量的值为-99.9000015258789因此,这个单元测试失败:

float f = -99.9f;
double d = (double)f;
Assert.AreEqual(-99.9, d);

我知道额外的32位被添加在不同的地方。然而,如果我直接赋值-99.9000000000000,则可以很好地表示为双精度类型,如下面的单元测试所示:

double d2 = -99.9;
Assert.AreEqual(-99.9, d2);
Assert.AreEqual(-99.9000000000000, d2);

所以我的最终问题是:是否有可能将-99.9f转换为double,使其真正等于-99.9000000000000 ?

编辑

我找到了一个解决方案,目前,似乎对我的特定应用程序很有效(即我不知道提前将浮点数四舍五入到多少位精度):

float f = -99.9f;
double d = double.Parse(f.ToString("r"));
Assert.AreEqual(-99.9, d);

我不确定这是否适用于所有情况;欢迎评论

编辑2 根据下面Lasse V. Karlsen的回答,解决方案是使用相当于AreEqual方法的MSTest:

Assert.AreEqual(-99.9, d, 0.00001);

注意,如果我在这个增量值上再加一个零,测试就会失败

如何在不添加额外数字的情况下将float转换为double

问题是99.9不能在SingleDouble中精确表示(对应于c#关键字floatdouble)。

这里的问题是。9必须写成2的幂的分数之和。因为底层的表示是位,我们只能对每个位位置说它是开的和关的,而分数端的每个位位置表示1/2^N,这意味着0.9可以这样表示:

1   1   1    1    1      1      1      1       1       1
- + - + - + -- + --- + ---- + ---- + ----- + ----- + ------ + ....
2   4   8   64   128   1024   2048   16384   32768   262144

最后,不管你有多少位,你的结果要么略低于0.9,要么略高于0.9,因为0.9不能用这样的位精确表示,但这就是浮点数的存储方式。

通过调用.ToString(),或仅Console.WriteLine,或"" + f,甚至在调试器中检查,您看到的屏幕表示都可能被舍入,这意味着您将看不到正在存储的确切数字,只能看到舍入/格式化如何返回它。

这就是为什么在比较浮点值时,应该总是使用"epsilon"比较。

你的单元测试基本上说"只要实际值与期望值完全相同,我们就可以",但是当处理浮点值时,这种情况很少发生。事实上,我想说,这种情况很少发生,你会立即发现它,除了一种情况,当你使用预期和实际的常量,它们是相同的类型。

相反,您应该编写代码来测试实际值与期望值是否足够接近,其中"足够接近"与可能值的范围相比非常小,并且可能在每种情况下都有所不同。

你不应该期望Single和Double可以表示完全相同的值,所以来回转换甚至可能最终返回不同的值。

还需要注意的是,在。net中,由于处理器内置的FPU部分通常可以以比存储类型更高的精度进行操作,因此在DEBUG和RELEASE构建中,浮点计算的行为略有不同,这意味着如果编译器/优化器/抖动最终使用FPU进行一系列计算,结果可能与编译器临时存储在变量中的值略有不同。

你应该这样写比较语句:

if (Math.Abs(actual - expected) < 0.000001) { ... }

其中0.000001 "足够接近"

对于许多单元测试框架,比如NUnit,你可以像这样要求比较器把这个考虑进去:

Assert.AreEqual(-99.9, d2, 0.000001);

同样,如果你想了解更多,这篇文章有很多血腥的细节:

  • 每个计算机科学家都应该知道的浮点运算

你看到的问题是由于浮动分辨率。

float的精度很低(取决于你的需要),即使你赋值-99.9000015258789f,浮点数也不会有这个值,它真的会有-99.9000015258789f作为值,因为当你把它转换为双精度时你会看到这个值,所以问题不是转换为双精度,而是使用浮点数。

如果你知道你的数字有多少位小数,你可以使用数学。Round(floatNumber, decimalPlaces)几乎可以解决所有问题,但对于某些值,它也会失败,这取决于您有多少小数位。

欢呼。