【问题标题】:Why does this formula break after 31 loops?为什么这个公式在 31 次循环后会中断?
【发布时间】:2021-03-15 13:59:21
【问题描述】:

我有以下代码:

public float SnapValueToCoreBlock(float ValueToSnap)
{
    ValueToSnap = ValueToSnap + 0.5f;
    ValueToSnap = Mathf.Floor(ValueToSnap);
    return ValueToSnap;
}

float floatvar;
int intvar;


    for (int z = 0; z < 100; z++)
    {

        floatvar = z + (Mathf.FloorToInt(0.499999f) + 1) * 0.499999f / (Mathf.FloorToInt(0.499999f) + 1);
        intvar = (int)SnapValueToCoreBlock(floatvar);
    }

我希望循环中的“intvar”始终等于“z”,但是在 31 次迭代后会出现某种舍入误差,并且当 z=32 intvar = 33 而不是 z=32 intvar =32

从那时起 intvar 总是减 1,因此对于 z31 intvar=z+1

我的预期结果是 z = intvar 总是,我不明白为什么当 z 达到 32 时这会任意改变,如果有人可以帮助我,我将不胜感激,提前致谢。

【问题讨论】:

  • 可能是四舍五入的问题,一旦你达到 32 位,你的小数位就会强制它对一个数字进行四舍五入。如果你达到 64 或其他东西,它会偏离 2 点吗?也许在不强制转换的情况下测试你的值,看看你的 31 数字是什么,看看它是否会四舍五入。
  • 对于 64 它仍然关闭 1,因此 z=64 给出 intvar=65,对于 z=99 intvar =100,并且 floatvar 始终显示 z.5,即对于 z=1 floatvar = 1.5 , 对于 z = 70 floatvar = 70.5
  • 我把 64 作为猜测扔了出去,可能是 70 或 80,或者当你施放它时计算可能会导致它四舍五入。我会调试并逐步查看我们第 31 次查看的所有数字,然后查看其中一个是否可能导致舍入问题(仍然不确定是否是这样,但是自从你铸造你的浮动后需要看看到 int)。
  • 如果你想使用浮点数,你不能使用像0.4999999f这样的数字。请记住,浮点数只有 6-9 位精度,而您有 7 位。
  • 我编辑了我的答案,如果你有兴趣

标签: c# unity3d math floating-point formula


【解决方案1】:

我将您的代码从 Unity 转换为 .NET Framework。这涉及将Mathf.FloorToInt(someFloat) 更改为(int)Math.Floor(someFloat)Mathf.Floor(ValueToSnap) 更改为(float)Math.Floor(ValueToSnap)。我相信它会做同样的事情,但它需要绕过double

我还在你的循环中插入了一个 WriteLine 语句:

for (int z = 0; z < 100; z++)
{
    floatvar = z + ((int)Math.Floor(0.499999f) + 1) * 0.499999f / ((int)Math.Floor(0.499999f) + 1);
    intvar = (int)SnapValueToCoreBlock(floatvar);
    Debug.WriteLine($"FloatVar: {floatvar} Z: {z} IntVar: {intvar}");
}

我看到了同样的行为。

特别是,我在z == 9 看到了休息:

FloatVar: 8.499999 Z: 8 IntVar: 8
FloatVar: 9.499999 Z: 9 IntVar: 9
FloatVar: 10.5 Z: 10 IntVar: 10
FloatVar: 11.5 Z: 11 IntVar: 11

z == 32:

FloatVar: 30.5 Z: 30 IntVar: 30
FloatVar: 31.5 Z: 31 IntVar: 31
FloatVar: 32.5 Z: 32 IntVar: 33
FloatVar: 33.5 Z: 33 IntVar: 34

即使我将输出的精度扩展到浮点数之外:

Debug.WriteLine($"FloatVar: {floatvar:0.0000000000000000000} Z: {z} IntVar: {intvar}");

我看到相同的行为,向下舍入到 32 以下并向上舍入。

那我大大简化了你的计算:

floatvar2 = z + 0.499999f;
intvar2 = (int)SnapValueToCoreBlock(floatvar2);

我仍然看到相同的行为。

所以,看起来是这样的:

 anInteger + 0.499999f + 0.5f;

对于anInteger &lt; 32 的值小于anInteger + 1.0f,对于anInteger &gt;= 32 的值等于或大于anInteger + 1.0f。而且,你知道吗,这并不让我感到惊讶。您正处于浮点精度的边缘(请记住,浮点数的精度约为 6-9 位:https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/floating-point-numeric-types)。当您从 31 变为 32 时,您最终设置了另一个位,这可能就是造成差异的原因。

最终评论 (作为更新)

您说“0.4999999 是我用于碰撞的变量”。您需要阅读如何比较浮点值,以及如何正确使用 epsilon。这是我发现的一件事 (https://bitbashing.io/comparing-floats.html)。它以 C++ 为重点,但似乎解决了这些问题。作为最后的评论,如果您曾经使用测量值(例如在化学过程控制系统中),则需要在建立 epsilon 时包含测量的精度。

【讨论】: