当前位置:  开发笔记 > 编程语言 > 正文

为什么在由parantheses分隔和由语句分隔时,C#中的浮点精度会有所不同?

如何解决《为什么在由parantheses分隔和由语句分隔时,C#中的浮点精度会有所不同?》经验,为你挑选了2个好方法。

我知道浮点精度在常规情况下是如何工作的,但我偶然发现了我的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");
        }
    }

当然,错误很小,只有在经过大量迭代后才出现,但它给我带来了一些很棒的问题.



1> Eric Lippert..:

亨克是完全正确的.只是为此添加一点.

这里发生的是,如果编译器生成的代码将浮点运算保持在"芯片上",那么它们可以以更高的精度完成.如果编译器生成的代码每隔一段时间就会将结果移回堆栈,那么每次执行此操作时,额外的精度都会丢失.

编译器是否选择生成更高精度的代码取决于各种未指定的细节:是否编译了调试或零售,是否在调试器中运行,浮点数是变量还是常量,是什么芯片架构特定的机器有,等等.

基本上,你保证32位精度或更好,但你永远不能预测你是否会比32位精度更好.因此,您需要不依赖于精确的32位精度,因为这不是我们给您的保证.有时我们会做得更好,有时候不会做得更好,如果你有时会免费获得更好的结果,不要抱怨它.

亨克说他找不到这方面的参考.它是C#规范的4.1.6节,其中规定:

可以以比操作的结果类型更高的精度执行浮点运算.例如,某些硬件体系结构支持"扩展"或"长双"浮点类型,其范围和精度比double类型更大,并使用此更高精度类型隐式执行所有浮点运算.只有在性能成本过高的情况下,才能使这种硬件架构以较低的精度执行浮点运算,而不是要求实现放弃性能和精度,C#允许更高精度的类型用于所有浮点运算.除了提供更精确的结果外,这几乎没有任何可衡量的影响.

至于你应该做什么:首先,总是使用双打.没有任何理由使用浮点数进行算术运算.如果需要,可以使用浮子进行存储 ; 如果你有一百万个并且想要使用四百万个字节而不是八百万个字节,这对于浮点数来说是合理的用法.但是在运行时花费成本,因为芯片经过优化可以进行64位数学运算,而不是32位数学运算.

其次,不要依赖浮点结果的准确性或可重现性.条件的微小变化可能导致结果的微小变化.


Re:浮在XBOX上:好的,这很有道理.我没有意识到他们的库只使用了浮点数.一个奇怪的选择.Re:具体计算的精度可以在调用之间改变吗?理论上,是的; 抖动被允许说"嘿,基于我从之前的运行中收集的数据,我发现如果我重新生成这样的代码,我可以提高速度和精度......"实际上,抖动实际上并没有那.(据我所知;我不是抖动方面的专家.)

2> Henk Holterm..:

我找不到支持这个的参考,但我认为这是由于以下原因:

浮点运算以硬件中可用的精度计算,这意味着它们的精度可以高于float.

对中间result2变量的赋值强制舍入回到浮点精度,但单个表达式for rsult1在向下舍入之前完全以原始精度计算.

另一方面,测试浮动或双重==是总是危险的.Microsoft单元测试提供了Assert.AreEqual(float expected, float actual,float delta)使用合适的delta解决此问题的方法.


夫妻评论.首先,"基于浮动epsilon"没有意义.Epsilon是浮点数可以表示的最小值; 该值实际上与舍入引起的误差的大小没有任何关系.用于允许增量的正确数量是将增量基于*表示的物理量*中的*测量误差.如果你说长度是76.23米,高度是2.44米,那么应该用测量精度的公差来计算面积:大约0.005.
使用双倍而且漂移需要40亿倍才能显着.至于完美的准确度:尝试将10除以3,然后乘以3.
推荐阅读
jerry613
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有