c# - 简单 float 计算错误

标签 c# floating-point

<分区>

我遇到了问题:

int q = 150;
float s = 0.7f;
float z = q*s;
int z1 = (int) (q*s);
int z2 = (int) z;

这导致

  • z1int,值为 104
  • z2int,值为 105

谁能解释一下?我不明白这些结果。


为避免关闭,我 (René Vogt) 添加了以下信息:

  • q*s 导致 float 的值为 105.0f(或者可能是 104.999999,但字符串表示最终为 105)。
  • 所以 z105
  • float

现在的问题是,为什么(int)z的结果是105,而(int)(q*s) 结果为 104?我可以在我的机器上重现它(i7、Win10、VS2015、.NET4.6.1)


和 IL 代码:

// Initialisation
// q = 150
ldc.i4 0x96  
stloc.0
// s = 0.7f
ldc.r4 0.69999999 
stloc.1

// calculating z 
ldloc.0 // load q
conv.r4 // convert to float
ldloc.1 // load s
mul     // q*s
stloc.2 // store float result to z

// calulating z1
ldloc.0 // load q
conv.r4 // convert to float
ldloc.1 // load s
mul     // q*s
conv.i4 // convert to int
stloc.3 // store int result in z1 => 104!

// calculating z2
ldloc.2 // loading float z
conv.i4 // converting to int
stloc.s // write to z2 (last local variable -> "s" as stack address)
        // => 105

所以我认为 z1z2 之间的唯一区别是对于 z2 中间 float 结果得到从寄存器写入局部变量的(z)存储位置。但这对结果有何影响?

最佳答案

数字 0.7 不能用 float 精确表示,s 的值更接近 0.699999988079071044921875.
qint 值将转换为 float,因为这可以直接表示为 150 .

如果将两者相乘,您将不会得到 105:

q = 150
s = 0.699999988079071044921875
q * s = 104.999998211861

现在引用CLI Spec (ECMA-335)中的相关部分§12.1.3:

When a floating-point value whose internal representation has greater range and/or precision than its nominal type is put in a storage location, it is automatically coerced to the type of the storage location. This can involve a loss of precision or the creation of an out-of-range value (NaN, +infinity, or -infinity). However, the value might be retained in the internal representation for future use, if it is reloaded from the storage location without having been modified. It is the responsibility of the compiler to ensure that the retained value is still valid at the time of a subsequent load, taking into account the effects of aliasing and other execution threads (see memory model (§12.6)). This freedom to carry extra precision is not permitted, however, following the execution of an explicit conversion (conv.r4 or conv.r8), at which time the internal representation must be exactly representable in the associated type.

因此 q * s 产生的值比 float 可以处理的精度更高。将其直接存储到 int 时:

var z1 = (int)(q * s);

该值永远不会被强制转换为 float 类型,而是直接转换为 int 并因此被截断为 104。

在所有其他示例中,值被转换为或存储在 float 中,因此转换为最接近的 float 值,即 105。

关于c# - 简单 float 计算错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42066772/

相关文章:

java - 精度损失 - Java

swift - 在 Swift 4 中,将 String 值类型转换为 Float 值的正确方法是什么?

c# - 如何在 C# 中更改语音合成器的语音或速度

c# - 如何在 Visual Studio 2015 中获取部分类型的所有部分定义?

c# - 使用 SSH.NET 上传到 SFTP 服务器失败,并出现 SftpPathNotFoundException : 'The system cannot find the path specified.'

c++ - 使用 Wfloat-equal 选项将 float 与 1 或 0 进行比较

c - 将 Float (IEEE-754) 打包为 uint64_t 的代码的标准化部分

c# - 连接两个数据表期间无效的匿名类型成员声明符

c# - 显示服务器端警报消息,然后重定向到新页面

math - 如何在没有大整数乘法的情况下获得 5**x 的前 x 个前导二进制数字