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

时间:2019-06-24 08:05:53

标签: c# delphi delphi-7

如何在Delphi中将类似Math.Round的c#与MidpointRounding.AwayFromZero一起使用?

相当于什么:

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

输出:2.13

在德尔福吗?

2 个答案:

答案 0 :(得分: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;

相反。

当然,即使对于 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;

当然,如果要在生产代码中使用此功能,则还要添加至少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;

但是我还没有完全测试它。 (目前它通过了900+ unit test cases,但我认为测试套件还不够。)

答案 1 :(得分: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;
  

rmNearest

     

2,13

     

-2,13

     

rmDown

     

2,13

     

-2,13

     

rmUp

     

2,13

     

-2,12

     

rmTruncate

     

2,13

     

-2,13