转换为 Int returns 与 C# 中正数的 FloorToInt 不同的值

Casting to Int returns different value than FloorToInt for positive number in c#

[编辑]我重写了评论中提到的整个示例。

    float value = 0.52631580f;

    float sum = 0;
    for (int i = 0; i < 10; i++)
    {
        float division = sum / value;
        int result = (int)division;

        Console.WriteLine(division.ToString("0.00000") + " - " + result.ToString("0.00000"));
        sum += value;
    }

结果与预期不符:

0,00000 - 0,00000
1,00000 - 1,00000
2,00000 - 2,00000
3,00000 - 3,00000
4,00000 - 4,00000
5,00000 - 5,00000
6,00000 - 5,00000 <---- It should be 6
7,00000 - 7,00000

如果我手动执行,(int)6.00000000000f <-- Returns 6,当然。

这怎么可能?

代码正确。并符合标准。我将 WriteLine 行更改为:

Console.WriteLine($"{sum:0.000} {value:0.000} {division:N18} - {result:0.00000}");

得到了

2.105 0.526 4.000000000000000000 - 4.00000
2.632 0.526 5.000000000000000000 - 5.00000
3.158 0.526 5.999999523162841797 - 5.00000

3.684 0.526 6.999999523162841797 - 6.00000

4.211 0.526 7.999999046325683594 - 7.00000

4.737 0.526 8.999999046325683594 - 8.00000

舍入错误意味着除法结果不完全是 6、7 或 8。只有 5 位数字,我得到:

3.158 0.526 6.00000 - 5.00000

3.684 0.526 7.00000 - 6.00000

4.211 0.526 8.00000 - 7.00000

4.737 0.526 9.00000 - 8.00000

这不是关于 C# 如何截断或舍入,而是关于如何格式化浮点数。符合 IEEE 标准的方法是在格式化时 舍入 到所需的精度。

呸——一时之间,我居然忘记了我对花车的了解。

PS:.NET Core 3 中引入了另一个有趣的 change at 15 digits。在此之前,字符串格式停止生成 15 位数字,这导致与值的相当多的混淆接近但不完全等于整数。

自 .NET Core 3 起,字符串格式化程序发出足够的数字来生成 the shortest roundtrippable string。使用

Console.WriteLine($"{division} - {result}");

产生:

4 - 4

5 - 5

5.9999995 - 5

6.9999995 - 6

7.999999 - 7

8.999999 - 8

这就是为什么他们在计算数学中教我们 从不 比较浮点数是否相等,而只检查绝对差异是否低于阈值。

有些事情我总是忘记并且在崩溃或无限循环后必须重新发现。

PPS: 舍入误差

生成小数位是因为 float 没有足够的精度来存储 sumdivision 而不四舍五入。

使用 $"{sum} {division} - {result}" 并从 float 切换到 double 只产生一个带小数位的结果:

3.1578948 6 - 6

3.6842105999999997 6.999999999999999 - 6

4.2105264 8 - 8

请注意,即使 sum 也有舍入误差。

只有 decimal 具有足够的精度来存储准确的结果并避免舍入错误:

2.63157900 5 - 5

3.15789480 6 - 6

3.68421060 7 - 7

4.21052640 8 - 8

4.73684220 9 - 9