[英]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.