.NET中ToString("f2")的四舍五入误差

7

你好,我有这段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函数。


可能是printf和类似函数如何区分float和double的重复问题。 - user9645477
6个回答

7
使用 BitConverter.GetBytes 并输出实际生成的字节,可以看出这不是编译器的差异 - 在两种情况下,存储的实际浮点值都是 0x403FAE14,这个 便利计算器 告诉我这是确切的值。
2.99499988555908203125

因此,区别必须存在于printfToString的不同行为中。除此之外,我现在无法立即说明更多内容。

2

我知道这是一个旧问题,但我正在回答它,因为我自己也理解了一些东西...

我的建议是,在应用格式之前,“printf”将浮点数转换为双精度。

我尝试将浮点数转换为双精度,然后查看双精度的ToString("f2")结果,它确实将值舍入到与浮点数相反的方向。

其他人也强调了关于printf的这个想法:

C自动将浮点值转换为双精度(当您调用接受可变参数的函数时进行标准转换,例如int printf...)

https://dev59.com/6Ws05IYBdhLWcg3wDtpn#7480244

https://dev59.com/WGw15IYBdhLWcg3wy-zO#6395747

我认为浮点数四舍五入的原因是2.995F不能完美地转换成二进制,等于2.99499989F,我们期望2.995会四舍五入。

我认为将浮点数复制到双精度中并舍入下来的原因是,作为双精度的2.995 <> 作为双精度的2.99499989,而且2.995实际双精度值大于该值(更接近其真实小数值的精度),因此我们期望该值会被舍入下来。

虽然2.99499989F似乎小于2.995,但它四舍五入到3.00可能看起来是错误的,但请记住,2.99499989是十进制而不是浮点数,您正在将其“转换”为浮点数,基本上是将十进制转换为二进制,这实际上是1和0,然后要求在十进制术语中进行舍入,这意味着必须进行转换。嗯,至少有我提到的两个基于10的值可以转换为该数字作为浮点数,其中最简单的是2.995。


0

这不是因为“浮点数”不够准确吗?

看看这个,当你使用一个十进制:

        // 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

当你使用它们时会发生什么?难道你不能打印输出以及代码,这样我们就不必将其粘贴到编译器中才能理解你的答案了吗? - jalf

0

第一个不正确地四舍五入了数字。

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

可能是一些浮点精度问题?实际上,2.9949... 四舍五入到 2.995 比 2.995 还要大吗?


0

浮点精度为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 - jalf
没问题。编译器必须在2.9950001239776611328125E0和2.99499988555908203125之间做出选择。只有这些值符合IEEE754标准的规定。 - Alexey Malistov

-1

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


正如AakashM所示,实际存储的值为2.99499988555908203125,这已经足够接近,ToString应该会给出正确的答案。 - jalf
当然,假设 ToString 只是添加 0.005 以四舍五入到最近的两位小数。在这种情况下,您会得到 2.99999989f,其表示为 0x40400000。这恰好是 3。 - Maurits Rijk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接