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)
浮点数表示为分母中为 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)),则不太可能受到它们的影响。