【问题标题】:.NET rounding error in ToString("f2")ToString("f2") 中的 .NET 舍入错误
【发布时间】:2010-01-18 10:58:58
【问题描述】:

您好,我在 C# 中有这段代码:

float n = 2.99499989f;
MessageBox.Show("n = " + n.ToString("f2", CultureInfo.InvariantCulture));

还有这段 C++ 代码:

float n = 2.99499989f;
printf("n = %.2f", n);

第一个输出 3.00
第二个输出 2.99

我不知道为什么会这样。

更新:

我也试过Objective-C NSLog,输出是2.99

我需要快速修复它,所以我使用了以下方法:

float n = 2.99499989f;
float round = (float)Math.Round(n, 2);
MessageBox.Show("round = " + round.ToString(CultureInfo.InvariantCulture));

此代码显示 2.99,但以双精度计算舍入。我找不到 Math.RoundF。

【问题讨论】:

标签: c# c++ rounding-error


【解决方案1】:

使用BitConverter.GetBytes 并打印出生成的实际字节表明这不是 编译器 差异 - 在这两种情况下,存储的实际浮点值都是0x403FAE14this handy calculator 告诉我的是准确值

2.99499988555908203125

因此,区别必须在于printfToString 的不同行为。不止于此,我无法立即说出来。

【讨论】:

    【解决方案2】:

    (我意识到这是一个老问题,但我正在回答这个问题,因为我自己也了解一些事情......)

    我的建议是“printf”在应用格式之前将浮点数转换为双精度数。 我尝试将浮点数转换为双精度数,然后查看双精度数的 ToString("f2") 结果,当浮点数的值被舍入时,它以相反的方向进行舍入。

    其他人再次强化了关于 printf 的这个想法:

    C 会自动将浮点值转换为双精度值(这是在调用带有可变参数的函数时进行的标准转换,例如 int printf...

    https://stackoverflow.com/a/7480244/119418

    https://stackoverflow.com/a/6395747/119418

    我认为浮点数向上取整的原因是因为 2.995F 不能完美地转换为二进制,因此 = 2.99499989F,我们预计 2.995 会向上取整。

    我相信将浮点数复制到双舍入的原因是因为 2.995 作为双精度 2.99499989 作为双精度,并且 2.995 实际双精度值大于该值(更接近其真实十进制值的精度),并且所以我们希望这个值会向下取整。

    即使看起来小于 2.995,2.99499989F 向上舍入到 3.00 似乎是错误的,但请记住,2.99499989 是小数,而不是浮点数,基本上你是在将它“转换”为浮点数将base-10转换为base-2,它实际上是1和0,然后要求它以base-10四舍五入,这意味着必须进行转换。好吧,我提到至少有 2 个以 10 为底的值可以转换为浮点数,其中最简单的是 2.995。

    【讨论】:

      【解决方案3】:

      第一个数字不正确。

      我猜第二个只选择逗号后的两位数。

      也许是一些浮点精度问题? 2.9949...实际上四舍五入到超过 2.995?

      【讨论】:

        【解决方案4】:

        浮点精度为2^{-23} 或大约0,0000001192(相对误差)。 看,2.995-2.99499989 = 0,00000011。相对误差为3,6*10^{-8}

        如果某些编译器读取了 2.99499989f 常量的所有数字,则结果是大于 2.995 的数字。但是如果另一个编译器只读取2.994999(因为精度小于 2^{-23} 并且最后一位不重要),那么结果是数字小于 2.995

        【讨论】:

        • 不确定你的意思。第一个编译器如何得到2.99499989f > 2.995
        • 没问题。编译器必须在 2.9950001239776611328125E0 和 2.99499988555908203125 之间进行选择。只有这些值对 IEEE754 有效。
        【解决方案5】:

        这不是因为“浮动”不是“那么”精确吗?

        检查一下,当你使用小数时:

                // float
                Console.WriteLine (2.99499989f.ToString ("f2"));
                // The above line outputs 3.00
        
                // decimal
                Console.WriteLine (2.99499989M.ToString ("f2"));
                // The above line outputs 2.99
        

        也许浮点数不能代表 2.99499989 或任何 2.99。
        看看当你这样做时会发生什么:

        // float
        Console.WriteLine (2.98499f.ToString ("f2"));
        // The above line outputs 2.99
        

        【讨论】:

        • 当您使用这些时会发生什么?你不能把输出和代码一样打印出来,这样我们就不必将它粘贴到编译器中来理解你的答案了吗?
        【解决方案6】:

        数字 2.99499989f 不能在 IEEE754 中准确表示。显然 printf 和 ToString 处理这种情况的方式不同。

        【讨论】:

        • 正如 AakashM 所示,实际存储的值是 2.99499988555908203125,这已经足够接近 ToString 应该给出正确答案了。
        • 当然,但我们假设 ToString 只是添加 0.005 以四舍五入到最接近的 2 位小数。在这种情况下,您会得到 2.99999989f,其表示为 0x40400000。恰好是 3。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2010-10-31
        • 1970-01-01
        • 2019-04-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多