代码之家  ›  专栏  ›  技术社区  ›  angularsen Bcelik

为什么用parantesses分隔和用语句分隔时,C中的浮点精度不同?

  •  5
  • angularsen Bcelik  · 技术社区  · 14 年前

    我知道在常规情况下浮点精度是如何工作的,但我在C代码中偶然发现了一个奇怪的情况。

    为什么result1和result2在这里不是完全相同的浮点值?

    
    const float A;   // Arbitrary value
    const float B;   // Arbitrary value
    
    float result1 = (A*B)*dt;
    
    float result2 = (A*B); 
    result2 *= dt;
    

    this page 我认为float算术是左关联的,这意味着值是以从左到右的方式计算和计算的。

    完整的源代码涉及xna的四元数。我认为这与我的常量和vectorhelper.addPitchRollYaw()的作用无关。如果我以同样的方式计算三角俯仰/侧倾/偏航角,则测试通过得很好,但由于代码如下所示,测试不通过:

    
    X
      Expected: 0.275153548f
      But was:  0.275153786f
    
    
    [TestFixture]
        internal class QuaternionPrecisionTest
        {
            [Test]
            public void Test()
            {
                JoystickInput input;
                input.Pitch = 0.312312432f;
                input.Roll = 0.512312432f;
                input.Yaw = 0.912312432f;
                const float dt = 0.017001f;
    
                float pitchRate = input.Pitch * PhysicsConstants.MaxPitchRate;
                float rollRate = input.Roll * PhysicsConstants.MaxRollRate;
                float yawRate = input.Yaw * PhysicsConstants.MaxYawRate;
    
                Quaternion orient1 = Quaternion.Identity;
                Quaternion orient2 = Quaternion.Identity;
    
                for (int i = 0; i < 10000; i++)
                {
                    float deltaPitch = 
                          (input.Pitch * PhysicsConstants.MaxPitchRate) * dt;
                    float deltaRoll = 
                          (input.Roll * PhysicsConstants.MaxRollRate) * dt;
                    float deltaYaw = 
                          (input.Yaw * PhysicsConstants.MaxYawRate) * dt;
    
                    // Add deltas of pitch, roll and yaw to the rotation matrix
                    orient1 = VectorHelper.AddPitchRollYaw(
                                    orient1, deltaPitch, deltaRoll, deltaYaw);
    
                    deltaPitch = pitchRate * dt;
                    deltaRoll = rollRate * dt;
                    deltaYaw = yawRate * dt;
                    orient2 = VectorHelper.AddPitchRollYaw(
                                    orient2, deltaPitch, deltaRoll, deltaYaw);
                }
    
                Assert.AreEqual(orient1.X, orient2.X, "X");
                Assert.AreEqual(orient1.Y, orient2.Y, "Y");
                Assert.AreEqual(orient1.Z, orient2.Z, "Z");
                Assert.AreEqual(orient1.W, orient2.W, "W");
            }
        }
    

    当然,错误很小,只在大量迭代之后才会出现,但它给我带来了一些巨大的麻烦。

    2 回复  |  直到 6 年前
        1
  •  7
  •   Henk Holterman    6 年前

    我找不到支持此操作的参考,但我认为原因如下:

    • 浮点运算以硬件中可用的精度计算,这意味着它们可以以比 float .
    • 对中间人的分配 result2 变量力舍入到浮点精度,但是 rsult1 在四舍五入之前完全以本机精度计算。

    另一方面,测试float或double == 总是很危险的。微软的单元测试为AM提供了 Assert.AreEqual(float expected, float actual,float delta) 你可以用合适的三角形来解决这个问题。

        2
  •  9
  •   Eric Lippert    14 年前

    亨克完全正确。只是为了增加一点。

    这里所发生的是,如果编译器生成的代码能使浮点运算保持在“芯片上”,那么它们就可以以更高的精度完成。如果编译器生成的代码每隔一段时间将结果移回堆栈,那么每次这样做时,额外的预处理就会丢失。

    编译器是否选择生成更高精度的代码取决于各种未指定的详细信息:是否编译了debug或retail、是否在调试器中运行、浮动是否在变量或常量中、特定机器的芯片体系结构有,等等。

    基本上,你可以保证32位或更好的精度,但你永远无法预测你是否会得到更好的32位精度。因此,您不需要依赖于32位精度,因为这不是我们给您的保证。有时我们会做得更好,有时不会,如果你有时免费得到更好的结果,不要抱怨。

    亨克说他找不到这方面的参考资料。C规范第4.1.6节规定:

    浮点运算可以是 以高于 操作的结果类型。为了 例如,一些硬件架构 支持延长或长双 更大范围的浮点类型 比双精度, 隐式地执行所有 使用此函数的浮点运算 高精度类型。只有在 过高的性能成本 硬件体系结构 使用执行浮点运算 精度较低,而且 要求执行失败 性能和精度,C# 允许更高精度的类型 用于所有浮点 操作.除了提供更多 精确的结果,这很少有 可衡量的影响。

    至于你应该做什么:首先,总是用双打。没有任何理由使用浮点运算。使用浮点数 存储 如果你想;如果你有一百万个,并且想使用400万字节而不是800万字节,那么这是浮点的合理用法。但是floats在运行时会让你付出代价,因为芯片被优化为可以做64位的数学运算,而不是32位的数学运算。

    第二,不要依赖于精确或可重复的浮点结果。条件的微小变化会导致结果的微小变化。