c# delphi delphi-7

c# - ¿Cuál es el equivalente de Math.Round() con MidpointRounding.AwayFromZero en Delphi?



delphi-7 (2)

¿Cómo uso c # similar Math.Round con MidpointRounding.AwayFromZero en Delphi?

¿Cuál será el equivalente de:

double d = 2.125; Console.WriteLine(Math.Round(d, 2, MidpointRounding.AwayFromZero));

Salida: 2.13

En Delphi?


Creo que la función SimpleRoundTo Delphi RTL hace esencialmente esto, al menos si el modo de redondeo FPU es "correcto". Lea detenidamente su documentación e implementación, y luego decida si es lo suficientemente bueno para sus propósitos.

Pero tenga en cuenta que configurar el modo de redondeo para una sola operación de redondeo como este está usando un cambio global para resolver un problema local. Esto podría causar problemas (subprocesos múltiples, bibliotecas, etc.).

Charla de bonificación: si la pregunta hubiera sido sobre el redondeo "regular" (a un entero), creo que probé un enfoque como

function RoundMidpAway(const X: Real): Integer; begin Result := Trunc(X); if Abs(Frac(X)) >= 0.5 then Inc(Result, Sign(X)); end;

en lugar.

Por supuesto, es posible escribir una función similar incluso para el caso general de n dígitos fraccionarios. (Pero tenga cuidado de manejar los casos de borde, desbordamientos, problemas de punto flotante, etc., correctamente).

Actualización: Creo que lo siguiente hace el truco (y es rápido):

function RoundMidpAway(const X: Real): Integer; overload; begin Result := Trunc(X); if Abs(Frac(X)) >= 0.5 then Inc(Result, Sign(X)); end; function RoundMidpAway(const X: Real; ADigit: integer): Real; overload; const PowersOfTen: array[-10..10] of Real = ( 0.0000000001, 0.000000001, 0.00000001, 0.0000001, 0.000001, 0.00001, 0.0001, 0.001, 0.01, 0.1, 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000 ); var MagnifiedValue: Real; begin if not InRange(ADigit, Low(PowersOfTen), High(PowersOfTen)) then raise EInvalidArgument.Create(''Invalid digit index.''); MagnifiedValue := X * PowersOfTen[-ADigit]; Result := RoundMidpAway(MagnifiedValue) * PowersOfTen[ADigit]; end;

Por supuesto, si usara esta función en el código de producción, también agregaría al menos 50 casos de prueba de unidad que prueban su corrección (para ejecutarse diariamente).

Actualización: Creo que la siguiente versión es más estable:

function RoundMidpAway(const X: Real; ADigit: integer): Real; overload; const FuzzFactor = 1000; DoubleResolution = 1E-15 * FuzzFactor; PowersOfTen: array[-10..10] of Real = ( 0.0000000001, 0.000000001, 0.00000001, 0.0000001, 0.000001, 0.00001, 0.0001, 0.001, 0.01, 0.1, 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000 ); var MagnifiedValue: Real; TruncatedValue: Real; begin if not InRange(ADigit, Low(PowersOfTen), High(PowersOfTen)) then raise EInvalidArgument.Create(''Invalid digit index.''); MagnifiedValue := X * PowersOfTen[-ADigit]; TruncatedValue := Int(MagnifiedValue); if CompareValue(Abs(Frac(MagnifiedValue)), 0.5, DoubleResolution * PowersOfTen[-ADigit]) >= EqualsValue then TruncatedValue := TruncatedValue + Sign(MagnifiedValue); Result := TruncatedValue * PowersOfTen[ADigit]; end;

pero no lo he probado completamente. (Actualmente pasa más de 900 casos de prueba de unidad , pero aún no considero que el conjunto de pruebas sea suficiente)


Lo que estás buscando es la función SimpleRoundTo en combinación con SetRoundMode . Como dice la documentación:

SimpleRoundTo devuelve el valor más cercano que tiene la potencia especificada de diez. En caso de que AValue esté exactamente en el medio de los dos valores más cercanos que tienen la potencia especificada de diez (arriba y abajo), esta función devuelve:

  • El valor hacia más infinito si AValue es positivo.

  • El valor hacia menos infinito si AValue es negativo y el modo de redondeo FPU no está establecido en rmUp

Tenga en cuenta que el segundo parámetro de la función es TRoundToRange que se refiere al exponente (potencia de 10) en lugar del número de dígitos fraccionales en el método Math.Round de .NET. Por lo tanto, para redondear a 2 lugares decimales se usa -2 como rango a rango.

uses Math, RTTI; var LRoundingMode: TRoundingMode; begin for LRoundingMode := Low(TRoundingMode) to High(TRoundingMode) do begin SetRoundMode(LRoundingMode); Writeln(TRttiEnumerationType.GetName(LRoundingMode)); Writeln(SimpleRoundTo(2.125, -2).ToString); Writeln(SimpleRoundTo(-2.125, -2).ToString); end; end;

rmNearest

2,13

-2,13

rmDown

2,13

-2,13

rmUp

2,13

-2,12

rmTruncate

2,13

-2,13