Saltar al contenido

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

Esta noticia fue aprobado por nuestros especialistas para asegurar la veracidad de nuestro post.

Solución:

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

Pero cuidado con eso ajuste el modo de redondeo para una sola operación de redondeo como esta utiliza un cambio global para resolver un problema local. Esto podría causar problemas (subprocesos múltiples, bibliotecas, etc.).

Charla adicional: si la pregunta hubiera sido sobre el redondeo “regular” (a un número entero), creo que habría intentado 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 cambio.

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

Actualizar: Creo que lo siguiente funciona (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).

Actualizar: yo creer 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 todavía no considero que el conjunto de pruebas sea suficiente).

Lo que está 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 de diez especificada. En caso AValue está exactamente en el medio de los dos valores más cercanos que tienen la potencia de diez especificada (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 de 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 fraccionarios en el método Math.Round de .NET. Por lo tanto, para redondear a 2 lugares decimales, usa -2 como rango de redondeo.

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;

rmMás cercano

2,13

-2,13

rmDown

2,13

-2,13

rmUp

2,13

-2,12

rmTruncar

2,13

-2,13

Te invitamos a proteger nuestra labor dejando un comentario y dejando una valoración te lo agradecemos.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *