2010-03-22 20 views
5

我知道浮點精度如何在常規情況下工作,但我偶然發現了我的C#代碼中的奇怪情況。爲什麼不同的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我計算浮動算術被左結合和,這意味着值進行評估,並在左到右的方式計算的。

完整的源代碼涉及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"); 
     } 
    } 

誠然,誤差小並且僅在大量的迭代之後呈現自己,但它已經給我帶來了一些很好的揹負。

回答

6

我無法找到一個參考來支持這一行動,但我認爲這是由於以下幾點:

  • 浮點運算都在硬件中可用的計算精度,這意味着他們是可以做到的比float更高的精度。
  • 對中間變量result2的賦值強制回到浮點精度,但rsult1的單個表達式在向下舍入之前完全以本機精度計算。

在附註中,使用==測試float或double會危險。 Microsoft單元測試提供了Assert.AreEqual(float expected, float actual,float delta),您可以通過合適的增量(基於float.Epsilon)來解決此問題。

+0

有趣的是,一位朋友提出這樣一個想法,即可能存在硬件指令直接採用兩次浮點乘法以提高精度,並且將兩個語句分開可防止編譯器使用此特殊指令? – angularsen 2010-03-22 10:12:43

+1

@Andreas,AFAIK沒有x86指令來做到這一點。 – 2010-03-22 10:15:38

+1

+1。我想補充一點,x86處理器的本地精度是80位的兩倍,x64的本地精度是64位的兩倍。通過允許編譯器在寄存器中保留部分計算,可以獲得最準確的結果。告訴編譯器將部分產品轉換回float _will_導致精度損失。 – 2010-03-22 10:30:00

9

亨克是完全正確的。只是爲了補充一點。

這裏發生的事情是,如果編譯器生成的代碼保持浮點運算「在芯片上」,那麼它們可以以更高的精度完成。如果編譯器生成的代碼每隔一段時間都會將結果移回堆棧,那麼每次都會丟失額外的預編碼。

編譯器是否選擇生成更高精度的代碼取決於各種未指定的細節:編譯調試還是零售,是否在調試器中運行,浮點數是變量還是常量,特定機器具有哪種芯片架構,等等。

基本上,你保證32位精度或更好,但你永遠不能預測你是否會得到比32位精度更好的。因此,您不需要依賴精確的32位精度,因爲這不是我們給您的保證。有時候我們會做得更好,有時候會更好,如果有時您可以免費獲得更好的結果,請不要抱怨。

亨克說,他找不到這方面的參考。這是第4.1節。C#的規範,其中規定的6:

浮點操作可以是 比 的結果類型的操作的精確度更高的執行。對於 例如,某些硬件結構 支持「擴展」或「長雙」 浮點型具有更大的範圍 和精度比double類型, 和隱式地使用該 更高的精度類型執行所有 浮點運算。僅在 性能開銷過大可以這樣 硬件體系結構中,以 作出執行與 較少精度浮點運算,而非 需要一個實現來喪失 性能和精度,C# 允許更高的精度類型是 用於所有浮點運算 的操作。除了提供更精確的結果之外,這很少有任何可衡量的影響。

至於你應該怎麼做:首先,總是使用雙打。沒有任何理由使用浮點數進行算術運算。如果你願意,可以使用漂浮物存儲;如果你有一百萬個並且想要使用四百萬字節而不是八百萬字節,那麼對於浮點數來說這是合理的用法。但是在運行時花費你的代價,因爲該芯片經過優化,可以執行64位數學運算,而不是32位數學運算。

其次,不要依賴浮點結果的準確性或可重現性。條件的細微變化可能會導致結果的微小變化。

+0

非常好,徹底的後續行動。所以我現在明白了,浮點運算通常不是確定性的,但是我可以依賴於完全相同的計算(和相同的輸入)在我的程序中是「確定性的」嗎?也就是說,計算之間的精度是否可能會變化?我之所以使用float是因爲我使用的是XNA遊戲工作室框架,它是浮動領土。可能是因爲Xbox 360和Zune使用相同的框架。 – angularsen 2010-03-23 08:29:39

+2

Re:漂浮在XBOX上:好的,這很有道理。我沒有意識到他們的庫只使用浮游物。一個奇怪的選擇。 Re:調用之間特定計算的精度是否可以改變?理論上,是的。抖動允許說「嘿,根據我從以前的運行中收集的數據,我發現如果我像這樣重新生成代碼,我可以更快速和更精確地......」實際上,抖動實際上並沒有那。 (據我所知,我不是抖動方面的專家。) – 2010-03-23 14:40:40

+0

IMO埃裏克關於浮點使用的評論應該在涵蓋C#(以及更一般的所有其他相關語言)的每本教科書中。 – Ben 2010-11-05 10:56:39

相關問題