繁体   English   中英

与Delphi中的MidpointRounding.AwayFromZero相当的Math.Round()是什么?

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

如何在Delphi中使用带有MidpointRounding.AwayFromZero c#类似Math.Round

什么相当于:

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

产量: 2.13

在德尔福?

我相信Delphi RTL的SimpleRoundTo函数基本上就是这样,至少在FPU舍入模式是“正确的”时。 请仔细阅读其文档和实现,然后确定它是否足够用于您的目的。

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

奖金喋喋不休:如果问题是关于“常规”舍入(到整数),我想我尝试过像

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多个单元测试用例 ,但我认为测试套件还不够。)

您正在寻找的是SimpleRoundTo功能与SetRoundMode的结合。 正如文件所说:

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

  • 如果AValue为正,则加上无穷大的值。

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

请注意,函数的第二个参数是TRoundToRange ,它指的是指数(幂为10)而不是.NET的Math.Round方法中的小数位数。 因此,要舍入到2位小数,请使用-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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM