我知道浮点精度在常规情况下是如何工作的,但我偶然发现了我的C#代码中的奇怪情况.
为什么result1和result2在这里不是完全相同的浮点值?
const float A; // Arbitrary value const float B; // Arbitrary value float result1 = (A*B)*dt; float result2 = (A*B); result2 *= dt;
从这个页面我认为浮动算术是左关联的,这意味着值以从左到右的方式进行评估和计算.
完整的源代码涉及XNA的Quaternions.我不认为我的常量是什么以及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"); } }
当然,错误很小,只有在经过大量迭代后才出现,但它给我带来了一些很棒的问题.
亨克是完全正确的.只是为此添加一点.
这里发生的是,如果编译器生成的代码将浮点运算保持在"芯片上",那么它们可以以更高的精度完成.如果编译器生成的代码每隔一段时间就会将结果移回堆栈,那么每次执行此操作时,额外的精度都会丢失.
编译器是否选择生成更高精度的代码取决于各种未指定的细节:是否编译了调试或零售,是否在调试器中运行,浮点数是变量还是常量,是什么芯片架构特定的机器有,等等.
基本上,你保证32位精度或更好,但你永远不能预测你是否会比32位精度更好.因此,您需要不依赖于精确的32位精度,因为这不是我们给您的保证.有时我们会做得更好,有时候不会做得更好,如果你有时会免费获得更好的结果,不要抱怨它.
亨克说他找不到这方面的参考.它是C#规范的4.1.6节,其中规定:
可以以比操作的结果类型更高的精度执行浮点运算.例如,某些硬件体系结构支持"扩展"或"长双"浮点类型,其范围和精度比double类型更大,并使用此更高精度类型隐式执行所有浮点运算.只有在性能成本过高的情况下,才能使这种硬件架构以较低的精度执行浮点运算,而不是要求实现放弃性能和精度,C#允许更高精度的类型用于所有浮点运算.除了提供更精确的结果外,这几乎没有任何可衡量的影响.
至于你应该做什么:首先,总是使用双打.没有任何理由使用浮点数进行算术运算.如果需要,可以使用浮子进行存储 ; 如果你有一百万个并且想要使用四百万个字节而不是八百万个字节,这对于浮点数来说是合理的用法.但是在运行时花费成本,因为芯片经过优化可以进行64位数学运算,而不是32位数学运算.
其次,不要依赖浮点结果的准确性或可重现性.条件的微小变化可能导致结果的微小变化.
我找不到支持这个的参考,但我认为这是由于以下原因:
浮点运算以硬件中可用的精度计算,这意味着它们的精度可以高于float
.
对中间result2
变量的赋值强制舍入回到浮点精度,但单个表达式for rsult1
在向下舍入之前完全以原始精度计算.
另一方面,测试浮动或双重==
是总是危险的.Microsoft单元测试提供了Assert.AreEqual(float expected, float actual,float delta)
使用合适的delta解决此问题的方法.