Soy consciente de cómo funciona la precisión de punto flotante en los casos normales, pero tropecé con una situación extraña en mi código C#.¿Por qué difiere la precisión de coma flotante en C# cuando está separada por parantheses y cuando está separada por declaraciones?
¿Por qué no result1 y result2 el mismo valor de coma flotante aquí?
const float A; // Arbitrary value
const float B; // Arbitrary value
float result1 = (A*B)*dt;
float result2 = (A*B);
result2 *= dt;
De this page que pensé flotar aritmética se asociativo por la izquierda y que esto significa valores se evalúan y se calculan de una manera de izquierda a derecha.
El código fuente completo implica XNA's Quaternions. No creo que sea relevante cuáles son mis constantes y qué hace VectorHelper.AddPitchRollYaw(). La prueba pasa bien si puedo calcular el terreno de juego delta/rollo/guiñada ángulos de la misma manera, pero a medida que el código está debajo de ella no pasa:
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");
}
}
Por supuesto, el error es pequeño y sólo se presenta después de una gran cantidad de iteraciones, pero me ha causado algunos dolores de cabeza.
Interesante, un amigo sugirió la idea de que podría haber una instrucción de hardware que toma dos multiplicaciones de flotantes directamente para mejorar la precisión y que al dividir en dos instrucciones, evita que el compilador use esta instrucción especial. – angularsen
@Andreas, AFAIK no hay instrucciones x86 para hacer eso. –
+1. Añadiría que la precisión nativa de los procesadores x86 es 80 bits doble, la precisión nativa de x64 es 64 bits doble. Obtiene los resultados más precisos al permitir que el compilador mantenga cálculos parciales en los registros. Si se le dice al compilador que convierta los productos parciales de nuevo en flotantes, causará pérdida de precisión. –