Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the equivalent of Math.Round() with MidpointRounding.AwayFromZero in Delphi?

How do I use c# similar Math.Round with MidpointRounding.AwayFromZero in Delphi?

What will be the equivalent of:

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

Output: 2.13

In Delphi?

like image 550
zig Avatar asked Jun 24 '19 08:06

zig


People also ask

What is MidpointRounding Awayfromzero?

Rounding away from zeroMidpoint values are rounded to the next number away from zero. For example, 3.75 rounds to 3.8, 3.85 rounds to 3.9, -3.75 rounds to -3.8, and -3.85 rounds to -3.9. This form of rounding is represented by the MidpointRounding.

How do you round to half?

When rounding to the nearest half, round the fraction to whichever half the fraction is closest to on the number line. If a fraction is equally close to two different halves, round the fraction up.

Does Math round round up or down?

If the number you are rounding is followed by 5, 6, 7, 8, or 9, round the number up. Example: 38 rounded to the nearest ten is 40. If the number you are rounding is followed by 0, 1, 2, 3, or 4, round the number down. Example: 33 rounded to the nearest ten is 30.


1 Answers

I believe the Delphi RTL's SimpleRoundTo function does essentially this, at least if the FPU rounding mode is "correct". Please read its documentation and implementation carefully, and then decide if it is good enough for your purposes.

But beware that setting the rounding mode for a single rounding operation like this is using a global change to solve a local problem. This might cause problems (multi-threading, libraries, etc.).

Bonus chatter: Had the question been about "regular" rounding (to an integer), I think I'd tried an approach like

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

instead.

Of course, it is possible to write a similar function even for the general case of n fractional digits. (But be careful to handle edge cases, overflows, floating-point issues, etc., correctly.)

Update: I believe the following does the trick (and is fast):

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;

Of course, if you'd use this function in production code, you'd also add at least 50 unit test cases that test its correctness (to be run daily).

Update: I believe the following version is more 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;

but I haven't fully tested it. (Currently it passes 900+ unit test cases, but I don't consider the test suite quite sufficient yet.)

like image 54
Andreas Rejbrand Avatar answered Oct 22 '22 10:10

Andreas Rejbrand