C# 和浮点数的恶作剧

本文关键字:恶作剧 浮点数 | 更新日期: 2023-09-27 18:32:38

在测试为什么我的程序没有按预期工作时,我尝试在即时窗口中输入似乎失败的计算。

Math.Floor(1.0f)
1.0 - correct

然而:

200f * 0.005f
1.0
Math.Floor(200f * 0.005f)
0.0 - incorrect

此外:

(float)(200f * 0.005f)
1.0
Math.Floor((float)(200f * 0.005f))
0.0 - incorrect

可能正在发生一些浮点损失,例如 0.99963 ≠ 1.00127。

我不介意存储较少的 pricise 值,但以非有损的方式,例如,如果有一种数字类型将值存储为整数,但只有小数点后三位,如果它可以使其高性能的话。

我认为对于此类误差,可能有更好的计算方法(n * 0.005f)。

编辑:

TY,一个解决方案:

Math.Floor(200m * 0.005m)

另外,据我了解,如果我不介意将 1/200 更改为 1/256,这将起作用:

Math.Floor(200f * 0.00390625f)

我正在使用的解决方案。这是我在我的程序中可以得到的最接近的,似乎工作正常:

float x = ...;
UInt16 n = 200;
decimal d = 1m / n;
... = Math.Floor((decimal)x * d)

C# 和浮点数的恶作剧

浮点数表示为分母中为 2 的分数。也就是说,您可以准确地表示 1/2、3/4 或 19/256。由于 .005 是 1/200,而 200 不是 2 的幂,相反,你得到的0.005f是最接近的分数,底部的幂为 2,可以放入 32 位浮点数。

小数将数字表示为分母中为 10 的幂的分数。与浮点数一样,当您尝试表示不符合该模式的数字时,它们会引入错误。 例如,1m/333m将为您提供最接近 1/333 的数字,该数字的分母为 10 的幂,有效数字为 29 或更少。由于 0.005 是 5/1000,这是 10 的幂,0.005m会给你一个精确的表示。你付出的代价是小数比浮点数大得多,速度慢得多。

您应该始终始终使用小数进行财务计算,而不是浮动。

> 问题是 0.005f 实际上是 0.004999999888241291046142578125... 所以小于0.005。这是最接近 0.005 的float值。当你把它乘以 200 时,你最终得到小于 1 的东西。

如果您改用decimal - 始终,而不是float转换 - 在这种情况下您应该没问题。所以:

decimal x = 0.005m;
decimal y = 200m;
decimal z = x * y;
Console.WriteLine(z == 1m); // True

但是,不要以为这意味着decimal具有"无限精度"。它仍然是精度有限的浮点类型 - 它只是一个浮点小点类型,因此 0.005 是完全可表示的。

如果不能容忍任何浮点精度问题,请使用 decimal

http://msdn.microsoft.com/en-us/library/364x0z75.aspx

最终,即使decimal也存在精度问题(它允许 28-29 位有效数字)。如果您在支持的范围内工作((-7.9 x 10^28 到 7.9 x 10^28)/(100^28)),则不太可能受到它们的影响。