我们现有的应用程序从文件中读取一些浮点数。这些数字由其他一些应用程序写入(我们称之为应用程序B )。此文件的格式很久以前就已修复(我们无法更改)。在此文件中,所有浮点数都以二进制表示形式保存为浮点数(文件中为4个字节)。
在我们的程序中,一旦我们读取数据,我们就会将浮点数转换为双精度数,并使用双精度数进行所有计算,因为计算量非常大,我们关注舍入误差的扩散。
我们注意到,当我们通过十进制转换浮点数时(参见下面的代码),我们得到的结果比直接转换时更精确。注意:应用程序B也在内部使用双精度数,只将它们作为浮点数写入文件中。假设应用程序B将数字0.012写入文件为float。如果我们在读取到十进制后转换它然后加倍我们得到0.012,如果我们直接转换它,我们得到0.0120000001043081。
这可以在不读取文件的情况下进行复制 - 只需要一个作业:
float readFromFile = 0.012f;
Console.WriteLine("Read from file: " + readFromFile);
//prints 0.012
double forUse = readFromFile;
Console.WriteLine("Converted to double directly: " + forUse);
//prints 0.0120000001043081
double forUse1 = (double)Convert.ToDecimal(readFromFile);
Console.WriteLine("Converted to double via decimal: " + forUse1);
//prints 0.012
通过十进制从float转换为double是否总是有益的,如果不是,在什么条件下有益?
编辑:应用程序B可以通过两种方式获取它保存的值:
答案 0 :(得分:13)
我们得到0.012
不,不。 float
和double
都不能完全代表3/250。你得到的是字符串格式化程序Double.ToString()
作为"0.012"
呈现的值。但这是因为格式化程序没有显示确切的值。
通过decimal
会导致舍入。将Math.Round
与您想要的舍入参数一起使用可能会快得多(更不用说更容易理解了)。如果您关心的是有效位数,请参阅:
对于它的价值,0.012f
(这意味着最接近0.012的32位IEEE-754值)正是
0x3C449BA6
或
0.012000000104308128
这是完全可表示为System.Decimal
。但是Convert.ToDecimal(0.012f)
不会给你那个确切的价值 - 每the documentation there is a rounding step。
此方法返回的
Decimal
值最多包含七位有效数字。如果value参数包含七位以上的有效数字,则使用舍入舍入为最接近的数字。
答案 1 :(得分:2)
尽管看起来很奇怪,但在某些情况下,通过十进制转换(使用Convert.ToDecimal(float)
)可能会有所帮助。
如果已知用户以十进制表示形式提供原始数字且用户输入的数字不超过7位有效数字,则会提高精确度。
为了证明这一点,我写了一个小程序(见下文)。以下是解释:
从OP回忆起这是一系列步骤:
我们的应用程序读取float并通过以下两种方式之一将其转换为double:
(a)直接赋值为double - 有效地将23位数字填充为具有二进制零的29位数(29个零位);
(b)通过(double)Convert.ToDecimal(float)
转换为十进制。
正如Ben Voigt正确地注意到Convert.ToDecimal(float)
(see MSDN in the Remark section)将结果舍入为7位有效小数位数。在Wikipedia的IEEE 754 article关于Single我们可以读到precision is 24 bits - equivalent to log10(pow(2,24)) ≈ 7.225 decimal digits.
因此,当我们转换为十进制时,我们会丢失0.225的十进制数字。
因此,在一般情况下,当没有关于双打的附加信息时,在大多数情况下转换为十进制会使我们失去一些精确度。
但是(!)如果有最初的附加知识(在作为浮点数写入文件之前),则双精度数为小数,且不超过7位数,十进制舍入中引入的舍入误差 (上面的步骤3(b))将补偿二进制舍入引入的舍入误差(在上面的步骤2中)。
在用于证明语句 用于通用案例 的程序中,我随机生成双精度数,然后将其转换为浮点数,然后将其直接转换回double(a), (b)通过十进制,然后我测量原始双倍和双倍(a)和双倍(b)之间的距离。如果double(a)比double(b)更接近原始,我增加pro-direct转换计数器,在相反的情况下我增加pro-viaDecimal计数器。我以1百万的循环进行。循环,然后我打印pro-direct与pro-viaDecimal计数器的比率。该比率约为3.7,即大约在5个中的4个情况下,通过十进制的转换将破坏该数字。
要证明 用户输入数字的情况 ,我使用了相同的程序,只有我将Math.Round(originalDouble, N)
应用于双打的更改。因为我从Random类中获取originalDoubles,它们都将在0和1之间,因此有效位数与小数点后的位数一致。我把这个方法放在一个循环中,由N从1个有效数字到用户输入的15个有效数字。然后我把它绘制在图表上。 (用户输入的有效数字的数量)(直接转换比通过十进制转换的次数多少)的依赖性。
正如您所看到的,对于1到7个键入的数字,通过Decimal的转换总是比直接转换更好。确切地说,对于一百万个随机数,只有1或2不能通过转换为十进制来改善。
以下是用于比较的代码:
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();
//Use the line below for the user-typed-in-numbers case.
//double origDouble = Math.Round(rnd.NextDouble(), numTypedDigits);
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();
}
答案 2 :(得分:1)
这是一个不同的答案 - 我不确定它比Ben的好(几乎肯定不是),但它应该产生正确的结果:
float readFromFile = 0.012f;
decimal forUse = Convert.ToDecimal(readFromFile.ToString("0.000"));
只要.ToString("0.000")
生成“正确”的数字(应该很容易进行抽查),那么您将获得可以使用的内容,而不必担心舍入错误。如果您需要更高的精确度,只需添加更多0
。
当然,如果你真的需要使用0.012f
以达到最大精度,那么这将无济于事,但如果是这种情况,那么你不希望从浮点数中转换它首先。