¿Cómo evitar problemas de redondeo al comparar valores de moneda en Delphi?
winapi floating-point (6)
El tipo de moneda en Delphi es un entero de 64 bits escalado en 1 / 10,000; en otras palabras, su incremento más pequeño es equivalente a 0.0001. No es susceptible a problemas de precisión de la misma manera que el código de coma flotante.
Sin embargo, si está multiplicando sus números de Moneda por tipos de coma flotante o dividiendo sus valores de Moneda, el redondeo debe resolverse en un sentido u otro. La FPU controla este mecanismo (se llama la "palabra de control"). La unidad matemática contiene algunos procedimientos que controlan este mecanismo: SetRoundMode en particular. Puedes ver los efectos en este programa:
{$APPTYPE CONSOLE}
uses Math;
var
x: Currency;
y: Currency;
begin
SetRoundMode(rmTruncate);
x := 1;
x := x / 6;
SetRoundMode(rmNearest);
y := 1;
y := y / 6;
Writeln(x = y); // false
Writeln(x - y); // 0.0001; i.e. 0.1666 vs 0.1667
end.
Es posible que una biblioteca de terceros que está utilizando establezca la palabra de control en un valor diferente. Es posible que desee establecer la palabra de control (es decir, el modo de redondeo) explícitamente en el punto de inicio de sus cálculos importantes.
Además, si sus cálculos se transfieren alguna vez al punto flotante simple y luego vuelven a la moneda, todas las apuestas están desactivadas, es muy difícil auditarlas. Asegúrese de que todos sus cálculos estén en Moneda.
AFAIK, tipo de moneda en Delphi Win32 depende de la precisión del punto flotante del procesador. Debido a esto, tengo problemas de redondeo cuando comparo dos valores de moneda, obteniendo resultados diferentes según la máquina.
Por ahora estoy usando la función SameValue pasando un parámetro de Epsilon = 0.009, porque solo necesito 2 dígitos decimales de precisión.
¿Hay alguna forma mejor de evitar este problema?
No, la moneda no es un tipo de punto flotante. Es un decimal de precisión fija, implementado con almacenamiento entero. Se puede comparar exactamente y no tiene los problemas de redondeo de, por ejemplo, Double. Por lo tanto, si ve valores inexactos en las variables de su moneda, el problema no es el tipo de moneda en sí, sino lo que está poniendo en ella. Lo más probable es que tenga un cálculo de coma flotante en otro lugar de su código. Como no muestra ese código, es difícil ayudarlo más con esta pregunta. Pero la solución, en términos generales, será redondear sus números flotantes a la precisión correcta antes de almacenarlos en la variable Moneda, en lugar de hacer una comparación inexacta en las variables Moneda.
Para evitar posibles problemas con el redondeo de moneda en Delphi, utilice 4 decimales.
Esto asegurará que nunca tengas problemas de redondeo cuando realices calcualtions con cantidades muy pequeñas.
"Been there. Done That. Written the unit tests."
Si su situación es como la mía, puede encontrar útil este enfoque. Trabajo principalmente en nómina. Si una empresa tiene 3 departamentos y quiere cobrar el costo de un empleado de manera equitativa entre esos tres departamentos, hay muchas ocasiones en que habrá problemas de redondeo.
Lo que he estado haciendo es recorrer los departamentos cobrando a cada uno un tercio del costo total y agregando el costo cargado a una variable subtotal (moneda). Pero cuando la variable de ciclo es igual al límite, en lugar de multiplicar por la fracción, restamos la variable de subtotal del costo total y lo ponemos en el último departamento. Dado que las entradas del diario que resultan de este proceso siempre tienen que equilibrarse, creo que siempre ha funcionado.
Ver hilo:
D7 / DUnit: todas las pruebas de CheckEquals (moneda, moneda) fallan repentinamente ...
https://forums.codegear.com/thread.jspa?threadID=16288
Parece que un cambio en nuestras estaciones de trabajo de desarrollo ha causado que la comparación de divisas falle. No hemos encontrado la causa raíz, pero en dos computadoras con Windows 2000 SP4 e independientemente de la versión de gds32.dll (InterBase 7.5.1 o 2007) y Delphi (7 y 2009), esta línea
TIBDataBase.Create(nil);
cambia el valor de 8087 palabra de control de $ 1372 a $ 1272 ahora.
Y todas las comparaciones de Monedas en pruebas unitarias fallarán con mensajes divertidos como
Expected: <12.34> - Found: <12.34>
El gds32.dll no se ha modificado, por lo que supongo que existe una dependencia en esta biblioteca a un DLL de terceros que modifica la palabra de control.
Una manera más rápida y segura de comparar dos valores currency
es, sin duda, asignar las variables a su representación interna de Int64
:
function CompCurrency(var A,B: currency): Int64;
var A64: Int64 absolute A;
B64: Int64 absolute B;
begin
result := A64-B64;
end;
Esto evitará cualquier error de redondeo durante la comparación (trabajando con * 10000 valores enteros), y será más rápido que la implementación basada en FPU predeterminada (especialmente en el compilador XE2 de 64 bits).
Vea este artículo para obtener información adicional.