versiones programming programacion edition community borland delphi compare delphi-2009

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 .