我写下面的代码:
int vat = (int)(invoice.total * 0.08f);
假设 invoice.total = 36000。那么
vat
必须是 2880 但是是 2879!我将代码更改为
float v = invoice.total * 0.08f;
int vat = (int)v;
现在
vat
具有正确的值(2880)。我想知道
()
有没有优先权!而且 float 也正好是 2880.0 不少于一点,所以不能进行舍入!
最佳答案
一个 float
拥有一些未显示的“隐藏”精度。试试看invoice.total.ToString("R")
,您可能会发现它不完全是 36000
.
或者,这可能是您的运行时为中间结果 invoice.total * 0.08f
选择“更广泛”的存储位置的结果,例如 64 位或 80 位 CPU 寄存器或类似位置。 .
编辑:您可以通过更改
(int)(invoice.total * 0.08f)
进入
(int)(float)(invoice.total * 0.08f)
额外类型转换,来自
float
至float
(原文如此!),看起来像一个无操作,但它确实强制运行时舍入并丢弃不需要的精度。这没有很好的记录。 [将提供引用。]您可能想阅读的相关主题:Are floating-point numbers consistent in C#? Can they be?你的例子实际上是典型的,所以我决定更详细一点。这些东西在 Differences Among IEEE 754 Implementations 部分中有很好的描述。这是 David Goldberg 的 What Every Computer Scientist Should Know About Floating-Point Arithmetic 的附录(由匿名作者撰写)。所以假设我们有这个代码:
static int SO_24548957_I()
{
float t = 36000f; // exactly representable
float r = 0.08f; // this is not representable, rounded
float temporary = t * r;
int v = (int)temporary;
return v; // always(?) 2880
}
一切似乎都很好,但我们决定将临时变量重构掉,所以我们写:
static int SO_24548957_II()
{
float t = 36000f; // exactly representable
float r = 0.08f; // this is not representable, rounded
int v = (int)(t * r);
return v; // could be 2880 or 2879 depending on strange things
}
和砰!我们程序的行为发生了变化。如果您为平台
x86
编译,您可以在大多数系统上看到更改(至少在我的系统上!) (或 Any CPU
并选择了 Prefer 32-bit
)。优化与否(发布或 Debug模式)在理论上可能是相关的,硬件架构当然也很重要。许多人完全惊讶于 2880 和 2879 在符合 IEEE-754 的系统上都是正确的答案,但请阅读我提供的链接。
为了详细说明“不可表示”的含义,让我们看看 C# 编译器遇到符号
0.08f
时必须做什么。 .因为路过float
(32 位二进制浮点)有效,我们将不得不在:10737418 / 2**27 == 0.079 999 998 2...
和
10737419 / 2**27 == 0.080 000 005 6...
在哪里
**
表示取幂(即“幂”)。由于第一个更接近所需的数学值,我们 必须选择那个。所以实际值比期望值小一点。现在,当我们进行乘法并想要存储在 Single
中时同样,作为乘法算法的一部分,我们还必须再次舍入以产生最接近(实际)因子 36000
的精确“数学”乘积的乘积表示。和 0.0799999982...
.在这种情况下,您很幸运,最近的 Single
实际上是 2880
确切地说,所以在我们的例子中,乘法过程涉及到这个值的舍入。因此,上面的第一个代码示例给出
2880
.然而,在上面的第二个代码示例中,乘法可能在一些处理许多位(通常为 64 或 80)的 CPU 硬件中完成(在运行时的选择下,我们无法真正帮助)。在这种情况下,任何两个 32 位浮点数的乘积,就像我们的一样,可以在不需要对最终结果进行四舍五入的情况下计算出来,因为 64 位或 80 位足以容纳两个 32 位浮点数的完整乘积。很明显这个产品小于
2880
自从0.0799999982...
小于 0.08
.因此,上面的第二种方法示例可以返回
2879
.为了比较,这段代码:
static int SO_24548957_III()
{
float t = 36000f; // exactly representable
float r = 0.08f; // this is not representable, rounded
double temporary = t * (double)r;
int v = (int)temporary;
return v; // always(?) 2879
}
总是给
2879
因为我们明确告诉编译器转换 Single
至Double
这意味着添加一堆二进制零,所以我们得到 2879
确定的情况。经验教训: (1) 对于二进制浮点,将子表达式分解为临时变量可能会改变结果。 (2) 使用二进制浮点,C# 编译器设置如
x86
与 x64
可能会改变结果。当然,正如大家到处说的,不要使用
float
。或 double
用于货币应用;使用 decimal
那里。
关于c# - 将 float 转换为 int 数将导致 int 无效,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24548957/