15 votes

Quel est l'équivalent de Math.Round() avec MidpointRounding.AwayFromZero dans Delphi ?

Comment utiliser c# similaire Math.Round con MidpointRounding.AwayFromZero dans Delphi ?

Ce qui sera l'équivalent de :

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

Sortie : 2.13

Dans Delphi ?

15voto

Andreas Rejbrand Points 57164

Je crois que les RTL de Delphi SimpleRoundTo fait essentiellement cela, du moins si le mode d'arrondi de la FPU est "correct". Veuillez lire attentivement sa documentation et son implémentation, et décidez ensuite si elle est suffisante pour vos besoins.

Mais attention paramètre le mode d'arrondi pour une seule opération d'arrondi comme celle-ci utilise un changement global pour résoudre un problème local. Cela peut causer des problèmes (multithreading, bibliothèques, etc.).

Bavardage en prime : Si la question avait porté sur l'arrondi "normal" (à un entier), je pense que j'aurais essayé une approche du genre

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

à la place.

Bien sûr, il est possible d'écrire une fonction similaire même pour le cas général de n des chiffres fractionnaires. (Mais veillez à traiter correctement les cas limites, les débordements, les problèmes de virgule flottante, etc.)

Mise à jour : Je pense que ce qui suit fait l'affaire (et est rapide) :

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;

Bien sûr, si vous utilisiez cette fonction dans un code de production, vous ajouteriez également au moins 50 cas de tests unitaires pour tester son exactitude (à exécuter quotidiennement).

Mise à jour : I croire la version suivante est plus stable :

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;

mais je ne l'ai pas entièrement testé. (Actuellement, il passe Plus de 900 cas de tests unitaires mais je ne considère pas que la suite de tests soit encore suffisante).

14voto

Peter Wolf Points 3265

Ce que vous cherchez, c'est SimpleRoundTo en combinaison avec SetRoundMode . Comme le dit la documentation :

SimpleRoundTo renvoie la valeur la plus proche qui a la puissance de dix spécifiée. Dans le cas AValue est exactement au milieu des deux valeurs les plus proches qui ont la puissance de dix spécifiée (au-dessus et au-dessous), cette fonction renvoie :

  • La valeur vers plus l'infini si AValue est positif.

  • La valeur vers moins l'infini si AValue est négatif et le mode d'arrondi de la FPU n'est pas défini sur rmUp

Notez que le deuxième paramètre de la fonction est TRoundToRange qui se réfère à l'exposant (puissance de 10) plutôt qu'au nombre de chiffres fractionnaires dans le langage .NET. Math.Round méthode. Par conséquent, pour arrondir à deux décimales, vous utilisez -2 comme intervalle d'arrondi.

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;

rmPlus proche

2,13

-2,13

rmDown

2,13

-2,13

rmUp

2,13

-2,12

rmTruncate

2,13

-2,13

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X