delphi - programming - Resultados extraños con valor de moneda/comparación de valor constante
delphi versiones (4)
Dado que no es posible realizar una conversión dura como la Moneda (1.32), puede usar lo siguiente para la conversión explícita
Function ToCurrency(d:Double):Currency;
begin
Result := d;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
C: Currency;
begin
C := 1.32;
if C < ToCurrency(1.32) then
begin
Writeln (''strange'');
end;
end;
Otra forma podría ser forzando el uso de Curreny por el uso de una constante o variable
const
comp:Currency=1.32;
var
C: Currency;
begin
C := 1.32;
if C < comp then
begin
writeln (''strange'');
end;
end;
Cuando se compila con Delphi 2009 y se ejecuta, esta aplicación de consola escribe "extraño". Los valores en ambos lados del operador "menor que" son iguales, pero el código se comporta como si no fueran iguales. ¿Qué puedo hacer para evitar este problema?
program Project5;
{$APPTYPE CONSOLE}
var
C: Currency;
begin
C := 1.32;
if C < 1.32 then
begin
WriteLn(''strange'');
end;
ReadLn;
end.
El código ps funciona bien con otros valores.
Esta answer de Barry Kelly explica que el tipo de moneda "no es susceptible a problemas de precisión de la misma manera que el código de punto flotante".
Esto parece ser una regresión en Delphi.
La salida es ''extraña'' en Delphi 2010. Pero en XE2 no hay salida, por lo que el error no está presente. No tengo XE a la mano para probar, pero gracias a @Sertac por confirmar que XE también produce "extraño". Tenga en cuenta que las versiones anteriores de Delphi también están bien, por lo que esta fue una regresión alrededor del tiempo D2009.
En 2010 el código generado es:
Project106.dpr.10: if C < 1.32 then
004050D6 DB2D18514000 fld tbyte ptr [$00405118]
004050DC DF2D789B4000 fild qword ptr [$00409b78]
004050E2 DED9 fcompp
004050E4 9B wait
004050E5 DFE0 fstsw ax
004050E7 9E sahf
004050E8 7319 jnb $00405103
Project106.dpr.12: WriteLn(''strange'');
El literal 1.32 se almacena como un valor de punto flotante de 10 bytes que debe tener el valor 13200. Este es un valor de punto flotante binario representable exactamente. El patrón de bits para 13200 almacenado como flotante de 10 bytes es:
00 00 00 00 00 00 40 CE 0C 40
Sin embargo, el patrón de bits almacenado en el literal a $ 00405118 es diferente y es ligeramente mayor que 13200
. El valor es:
01 00 00 00 00 00 40 CE 0C 40
Y eso explica por qué C < 1.32
evalúa como True
.
En XE2 el código generado es:
Project106.dpr.10: if C < 1.32 then
004060E6 DF2DA0AB4000 fild qword ptr [$0040aba0]
004060EC D81D28614000 fcomp dword ptr [$00406128]
004060F2 9B wait
004060F3 DFE0 fstsw ax
004060F5 9E sahf
004060F6 7319 jnb $00406111
Project106.dpr.12: WriteLn(''strange'');
Observe aquí que el literal se mantiene en una flotación de 4 bytes. Esto se puede ver por el hecho de que comparamos con dword ptr [$00406128]
. Y si observamos el contenido del flotador de precisión simple almacenado en $00406128
, encontramos:
00 40 4E 46
Y eso es exactamente 13200 como se representa como un flotador de 4 bytes.
Mi conjetura es que el compilador en 2010 hace lo siguiente cuando se enfrenta a 1.32
:
- Convertir 1.32 al flotador de 10 bytes exactamente más cercano.
- Multiplique ese valor por 10000.
- Almacene el flotador de 10 bytes resultante a
$00405118
.
Como 1.32 no es exactamente representable, resulta que el flotador final de 10 bytes no es exactamente 13200. Y presumiblemente la regresión se produjo cuando el compilador cambió de almacenar estos literales en flotadores de 4 bytes para almacenarlos en flotadores de 10 bytes.
El problema fundamental es que el soporte de Delphi para el tipo de datos de Currency
se basa en un diseño totalmente defectuoso. El uso de la aritmética de punto flotante binario para implementar un tipo de datos de punto fijo decimal es simplemente pedir problemas. La única forma sensata de arreglar el diseño sería rediseñar completamente el compilador para que use aritmética de enteros de punto fijo. Es bastante decepcionante notar que el nuevo compilador de 64 bits usa el mismo diseño que el compilador de 32 bits.
Para ser honesto contigo, detendría al compilador de Delphi haciendo cualquier trabajo de punto flotante con literales de Currency
. Es solo un campo minado completo. Haría el cambio de 10.000 en mi cabeza así:
function ShiftedInt64ToCurrency(Value: Int64): Currency;
begin
PInt64(@Result)^ := Value;
end;
Y luego el código de llamada sería:
C := 1.32;
if C < ShiftedInt64ToCurrency(13200) then
Writeln (''strange'');
¡No hay manera de que el compilador arruine eso!
¡Humph!
Para agregar a la respuesta de David, el siguiente código no es extraño, aunque es equivalente al código OP:
program Project2;
{$APPTYPE CONSOLE}
var
I: Int64;
E: Extended;
begin
I:= 13200;
E:= 13200;
if I < E then
begin
WriteLn(''strange'');
end;
ReadLn;
end.
Ahora el compilador genera un valor binario correcto para Extended (13200), por lo que el problema parece estar relacionado con una mala implementación del tipo de Currency
en el compilador de Delphi.
Para evitar este problema (error en el compilador), puede hacer lo que sugiere @bummi, o probar este tiempo de ejecución:
if C < Currency(Variant(1.32)) then
Para evitar el viaje de ida y vuelta a la FPU (y los errores de redondeo), considere usar esta función de comparación:
function CompCurrency(const A,B: Currency): Int64;
var
A64: Int64 absolute A; // Currency maps internally as an Int64
B64: Int64 absolute B;
begin
result := A64-B64;
end;
...
if CompCurrency(C,1.32) < 0 then
begin
WriteLn(''strange'');
end;
Consulte esta página para obtener más información, Floating point and Currency fields
.