尽管看起来很奇怪,但通过十进制转换(使用
Convert.ToDecimal(float)
)在某些情况下可能是有益的。
如果已知原始数字是由用户提供的十进制表示,并且用户最多输入了7个有效数字,则它将提高精度。
为了证明这一点,我编写了一个小程序(见下文)。以下是解释:
正如您从 OP 中回想起的那样,这是一系列步骤:
应用程序B有两个来源的双精度数:(a) 计算结果;(b) 从用户键入的十进制数转换而来。
应用程序B将其双精度数写成浮点数并存储到文件中,实际上是将52位二进制数字(
IEEE 754单精度)进行二进制舍入处理,变成23位二进制数字(
IEEE 754双精度)。
我们的应用程序通过以下两种方式之一读取该浮点数并将其转换为双精度数:
(a) 直接赋值给双精度数 - 实际上是用二进制零填充23位数字,使其变成52位数字(29个零位);
(b) 通过使用
(double)Convert.ToDecimal(float)
进行十进制转换。
正如Ben Voigt所指出的那样,Convert.ToDecimal(float)(参见MSDN的Remark部分)会将结果四舍五入到7个有效十进制数字。在维基百科的IEEE 754文章中,我们可以看到精度为24位-相当于log10(pow(2,24))≈7.225个十进制数字。因此,当我们进行转换为十进制时,我们失去了0.225个十进制数字。
因此,在一般情况下,如果没有关于双精度浮点数的其他信息,转换为十进制通常会使我们失去一些精度。
但是!如果有额外的知识表明,最初(在写入文件为浮点数之前),双精度浮点数是不超过7位数字的十进制数,则十进制舍入引入的舍入误差(上面的第3(b)步)将弥补二进制舍入引入的舍入误差(在第2步中)。
在证明语句的
一般情况的程序中,我随机生成双精度数,然后将其转换为单精度数,再将其转换回双精度数(a)直接转换,(b)通过十进制转换。然后我测量原始双精度数与双精度数(a)和双精度数(b)之间的距离。如果双精度数(a)比双精度数(b)更接近原始值,则增加直接转换计数器;反之,如果是通过十进制转换更接近原始值,则增加通过十进制转换计数器。我将这个过程循环1百万次,然后打印出直接转换计数器与通过十进制转换计数器的比率。这个比率大约为3.7,即大约有5次中有4次通过十进制转换会破坏数字。
为了证明“当用户输入数字时”的情况,我使用了同一个程序,唯一的改变是将
Math.Round(originalDouble, N)
应用于双精度浮点数。因为我从Random类中获得originalDoubles,它们都在0和1之间,所以有效数字的数量与小数点后的位数相同。我通过循环将此方法放置在由用户键入的1位有效数字至15位有效数字之间的N上,然后在图表上绘制出来。即(直接转换比通过十进制转换更好的次数)与用户键入的有效数字数量之间的关系如下图所示:
![The dependency of (how many times direct conversion is better than via decimal) from the number of significant digits typed by user](https://istack.dev59.com/GFpiJ.webp)
。
正如您所看到的,对于键入的1到7个数字,通过Decimal转换总是优于直接转换。确切地说,对于一百万个随机数,只有1或2个无法通过转换为Decimal而得到改进。
以下是进行比较所使用的代码:
private static void CompareWhichIsBetter(int numTypedDigits)
{
Console.WriteLine("Number of typed digits: " + numTypedDigits);
Random rnd = new Random(DateTime.Now.Millisecond);
int countDecimalIsBetter = 0;
int countDirectIsBetter = 0;
int countEqual = 0;
for (int i = 0; i < 1000000; i++)
{
double origDouble = rnd.NextDouble();
float x = (float)origDouble;
double viaFloatAndDecimal = (double)Convert.ToDecimal(x);
double viaFloat = x;
double diff1 = Math.Abs(origDouble - viaFloatAndDecimal);
double diff2 = Math.Abs(origDouble - viaFloat);
if (diff1 < diff2)
countDecimalIsBetter++;
else if (diff1 > diff2)
countDirectIsBetter++;
else
countEqual++;
}
Console.WriteLine("Decimal better: " + countDecimalIsBetter);
Console.WriteLine("Direct better: " + countDirectIsBetter);
Console.WriteLine("Equal: " + countEqual);
Console.WriteLine("Betterness of direct conversion: " + (double)countDirectIsBetter / countDecimalIsBetter);
Console.WriteLine("Betterness of conv. via decimal: " + (double)countDecimalIsBetter / countDirectIsBetter );
Console.WriteLine();
}