如何在不添加额外数字的情况下将float转换为double?
我遇到了一个有趣的问题:当我将float
值-99.9f
转换为double
变量时,该变量的值为-99.9000015258789
因此,此unit testing失败:
float f = -99.9f; double d = (double)f; Assert.AreEqual(-99.9, d);
我知道在不同的地方添加了额外的32位。 然而,我所追求的值, -99.9000000000000
,如果我直接指定它就表示为双-99.9000000000000
,如unit testing所示:
double d2 = -99.9; Assert.AreEqual(-99.9, d2); Assert.AreEqual(-99.9000000000000, d2);
所以我的最终问题是:是否可以采用-99.9f
并将其转换为double
-99.9000000000000
,使其真正等于-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);
请注意,如果我要向该delta值添加一个零,则测试将失败
问题是99.9无法在Single
或Double
完全表示(对应于C#关键字float
和double
)。
这里的问题是.9必须写成两个幂的分数之和。 因为底层表示是比特,我们只能为每个比特位置说它是开和关,并且分数侧的每个比特位置意味着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”比较。
您的unit testing基本上说“只要实际值与预期值完全相同,我们就可以了”,但在处理浮点值时,这种情况很少发生。 事实上,我会说它很少会发生,除了一件事,当你使用常量作为预期和实际并且它们是相同的类型时,你会立即发现它。
相反,您应该编写代码来测试实际值是否足够接近预期值,其中“足够接近”与可能值的范围相比非常小,并且对于每种情况可能会有所不同。
您不应该期望Single和Double可以表示完全相同的值,因此来回转换甚至可能最终返回不同的值。
还要注意,对于.NET,浮点计算在DEBUG和RELEASE构建方面表现略有不同,因为处理器的内置FPU部分通常可以比存储类型更高的精度运行,这意味着如果编译器/优化器/ jitter最终使用FPU进行一系列计算,结果可能与值暂时存储在编译器生成的变量中时略有不同。
所以这就是你写比较的方式:
if (Math.Abs(actual - expected) < 0.000001) { ... }
其中0.000001
“足够接近”。
就许多unit testing框架而言,比如NUnit,您可以要求比较将这一点考虑在内:
Assert.AreEqual(-99.9, d2, 0.000001);
此外,如果您想了解更多相关信息,本文有很多细节:
- 每个计算机科学家应该知道的浮点运算
您看到的问题是浮动分辨率。
浮点数具有较低的精度(取决于您的需求),即使您指定-99.9f浮点数也不具有该值,它实际上将具有-99.9000015258789f作为值,因为当您将其转换为double时,您会看到该值,所以问题不是转换为double,而是使用float。
如果你知道你的数字会有多少小数位,你可以使用Math.Round(floatNumber,decimalPlaces),它几乎可以解决所有问题,但对于某些值,它也会失败,这取决于你有多少小数位。
干杯。