在Delphi中,Math.Round()与MidpointRounding.AwayFromZero等效吗?

zig*_*zig 13 c# delphi delphi-7

如何使用C#类似Math.RoundMidpointRounding.AwayFromZero在Delphi?

相当于什么:

double d = 2.125;
Console.WriteLine(Math.Round(d, 2, MidpointRounding.AwayFromZero));
Run Code Online (Sandbox Code Playgroud)

输出: 2.13

在德尔福?

And*_*and 13

我相信,至少在FPU舍入模式为“正确”的情况下,Delphi RTL的SimpleRoundTo函数基本上可以做到这一点。请仔细阅读其文档和实现,然后确定它是否足以满足您的目的。

但是请注意,为这样的单个舍入操作设置舍入模式是使用全局更改来解决局部问题。这可能会导致问题(多线程,库等)。

有趣的是:如果问题是关于“常规”舍入(整数)的,我想我尝试过一种方法

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

代替。

当然,即使对于n个小数位的一般情况,也可以编写类似的函数。(但是要小心处理边缘情况,溢出,浮点问题等。)

更新:我相信以下方法可以达到目的(并且很快):

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;
Run Code Online (Sandbox Code Playgroud)

当然,如果要在生产代码中使用此功能,则还要添加至少50个单元测试用例,以测试其正确性(每天运行)。

更新:相信以下版本会更稳定:

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;
Run Code Online (Sandbox Code Playgroud)

但是我还没有完全测试它。(目前,它通过了900多个单元测试用例,但我认为测试套件还不够用。)


Pet*_*olf 12

什么你要找的是SimpleRoundTo与组合功能SetRoundMode。如文档所述:

SimpleRoundTo返回具有10的指定幂的最接近的值。如果AValue恰好在两个具有指定的10的幂的最接近值的中间(上方和下方),则此函数返回:

  • 朝向正无穷大的值AValue是正数。

  • 朝向负无穷大的值如果AValue为负且FPU舍入模式未设置为rmUp

请注意,该函数的第二个参数是TRoundToRange指指数(10的幂),而不是.NET Math.Round方法中的小数位数字。因此,要舍入到小数点后两位,请使用-2作为舍入范围。

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;
Run Code Online (Sandbox Code Playgroud)

rm最近

2,13

-2,13

下降

2,13

-2,13

rmUp

2,13

-2,12

rmTruncate

2,13

-2,13

  • 但是请注意,为此设置舍入模式是使用全局更改来解决局部问题。这可能会导致问题(多线程,库等)。 (8认同)
  • @AndreasRejbrand 没错。Delphi 7(甚至更新的版本)受到这些全局状态依赖例程的困扰,正如评论和其他答案中所指出的那样,应该谨慎使用它。 (2认同)