8

我们现有的应用程序从文件中读取一些浮点数。这些数字是由其他一些应用程序(我们称之为Application B)写在那里的。该文件的格式很久以前就已修复(我们无法更改)。在此文件中,所有浮点数都以二进制表示形式保存为浮点数(文件中为 4 个字节)。

在我们的程序中,一旦我们读取数据,我们就会将浮点数转换为双精度数,并在所有计算中使用双精度数,因为计算非常广泛,而且我们关心舍入误差的传播。

我们注意到,当我们通过十进制转换浮点数时(参见下面的代码),我们得到的结果比直接转换时更精确。注意:应用程序 B 在内部也使用双精度,并且仅将它们作为浮点数写入文件。假设应用程序 B 将数字 0.012 作为浮点数写入文件。如果我们在读取后将其转换为十进制,然后再将其翻倍,我们得到的正是 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

通过十进制将浮点数转换为双精度总是有益的,如果不是,在什么条件下它是有益的?

编辑:应用程序 B 可以通过两种方式获取它保存的值:

  1. 值可以是计算的结果
  2. 值可以由用户输入为小数部分(因此在上面的示例中,用户在编辑框中输入了 0.012 并将其转换为双精度,然后保存为浮点数)
4

3 回答 3

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)不会给你那个确切的值 - 根据文档有一个舍入步骤

此方法返回的Decimal值最多包含七个有效数字。如果 value 参数包含超过 7 个有效数字,则使用舍入到最接近的方式对其进行舍入。

于 2012-11-05T22:15:31.253 回答
2

尽管看起来很奇怪,但Convert.ToDecimal(float)在某些情况下,通过小数(带 )进行转换可能是有益的。

如果知道原始数字是由用户以十进制表示形式提供的并且用户键入的有效数字不超过 7 个,它将提高精度。

为了证明这一点,我编写了一个小程序(见下文)。这是解释:

正如您从 OP 中回忆的那样,这是一系列步骤:

  1. 应用程序 B 的双精度数来自两个来源: (a) 计算结果;(b) 从用户输入的十进制数转换而来。
  2. 应用程序 B 将其双精度值作为浮点数写入文件 - 有效地进行从 52 个二进制数字(IEEE 754 single)到 23 个二进制数字(IEEE 754 double)的二进制舍入。
  3. 我们的应用程序读取该浮点数并通过以下两种方式之一将其转换为双精度数:

    (a) 直接赋值给 double - 有效地将 23 位数字填充为具有二进制零(29 个零位)的 52 位数字;

    (b) 通过用 转换为十进制(double)Convert.ToDecimal(float)

正如 Ben Voigt 正确注意到的那样Convert.ToDecimal(float)请参阅备注部分中的 MSDN)将结果四舍五入为 7 位有效十进制数字。在维基百科关于 Single 的IEEE 754 文章中,我们可以读到,precision is 24 bits - equivalent to log10(pow(2,24)) ≈ 7.225 decimal digits.因此,当我们转换为十进制时,我们会丢失十进制数字的 0.225。

所以,在一般情况下,当没有关于双精度的附加信息时,转换为十进制在大多数情况下会使我们失去一些精度。

但是(!)如果有额外的知识,最初(在作为浮点数写入文件之前)双精度数是不超过 7 位的小数,则在十进制舍入中引入的舍入错误(上面的步骤 3(b))将补偿二进制舍入引入的舍入误差(在上面的步骤 2. 中)。

在证明通用情况下的语句的程序中,我随机生成双精度数,然后将其转换为浮点数,然后将其直接转换回双精度(a),(b)通过十进制,然后我测量原始双精度与双 (a) 和双 (b)。如果 double(a) 比 double(b) 更接近原始值,我会增加 pro-direct 转换计数器,在相反的情况下,我会增加 pro-viaDecimal 计数器。我在 100 万的循环中进行。循环,然后我打印 pro-direct 与 pro-viaDecimal 计数器的比率。结果证明该比率约为 3.7,即大约在 5 分之四的情况下,通过十进制转换会破坏该数字。

为了证明用户输入数字时的情况,我使用了相同的程序,唯一的更改是应用于Math.Round(originalDouble, N)双打。因为我从 Random 类中得到 originalDoubles,它们都将在 0 和 1 之间,所以有效位数与小数点后的位数一致。我把这个方法放在一个循环中,从用户输入的 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();
}
于 2012-11-06T22:29:27.957 回答
1

这是一个不同的答案 - 我不确定它是否比 Ben 的更好(几乎可以肯定不是),但它应该会产生正确的结果:

float readFromFile = 0.012f;
decimal forUse = Convert.ToDecimal(readFromFile.ToString("0.000"));

只要.ToString("0.000")产生“正确”的数字(应该很容易抽查),那么您就会得到可以使用的东西,而不必担心舍入错误。如果您需要更高的精度,只需添加更多0的 's.

当然,如果您确实需要达到0.012f最大精度,那么这将无济于事,但如果是这种情况,那么您首先不想将其从浮点数转换。

于 2012-11-06T15:04:29.357 回答