c# - 将 float 转换为 int 数将导致 int 无效

标签 c# math floating-point int type-conversion

我写下面的代码:

 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)

额外类型转换,来自 floatfloat (原文如此!),看起来像一个无操作,但它确实强制运行时舍入并丢弃不需要的精度。这没有很好的记录。 [将提供引用。]您可能想阅读的相关主题: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因为我们明确告诉编译器转换 SingleDouble这意味着添加一堆二进制零,所以我们得到 2879确定的情况。

经验教训: (1) 对于二进制浮点,将子表达式分解为临时变量可能会改变结果。 (2) 使用二进制浮点,C# 编译器设置如 x86x64可能会改变结果。

当然,正如大家到处说的,不要使用float。或 double用于货币应用;使用 decimal那里。

关于c# - 将 float 转换为 int 数将导致 int 无效,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24548957/

相关文章:

java - 蒙特卡罗标准差方程

google-sheets - Google 表格小于或等于 (<=) 提供错误结果

c# - 强制 float 在 .NET 中是确定性的?

c# - 是否有任何C#方式[获取]/[提供声音频率信息的库?

c# - 检查文件是否正在使用 - 附加问题

c# - 同时在 Azure Blob 存储中创建同名的 Blob

algorithm - 多边形裁剪 : Only "viewable" area

java - 我无法理解 PhotoView 的 getDisplayRect() 实际返回的内容(构建 android 照片裁剪工具)

将 float 转换为字节数组的 C 函数

c# - Entity Framework 更改跟踪(只读)查询