簡體   English   中英

為什么在由parantheses分隔和由語句分隔時,C#中的浮點精度會有所不同?

[英]Why differs floating-point precision in C# when separated by parantheses and when separated by statements?

我知道浮點精度在常規情況下是如何工作的,但我偶然發現了我的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變量的賦值強制舍入回到float精度,但rsult1的單個表達式在向下舍入之前完全以本機精度計算。

另外,使用==測試float或double總是很危險的。 Microsoft Unit測試提供了Assert.AreEqual(float expected, float actual,float delta) ,您可以使用合適的delta解決此問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM