我怎样在Delphi中使用类似于Math.Round并带有MidpointRounding.AwayFromZero
的功能?
以下代码将等价于什么:
double d = 2.125;
Console.WriteLine(Math.Round(d, 2, MidpointRounding.AwayFromZero));
输出:2.13
在Delphi中?
我怎样在Delphi中使用类似于Math.Round并带有MidpointRounding.AwayFromZero
的功能?
以下代码将等价于什么:
double d = 2.125;
Console.WriteLine(Math.Round(d, 2, MidpointRounding.AwayFromZero));
输出:2.13
在Delphi中?
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;
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+单元测试用例,但我认为测试套件还不够充分。)
RoundMidpAway(const X: Real): Integer;
内联化是个不错的主意。 - Andreas RejbrandRoundMidpAway(2.135, -2)
results 2.13
. should be 2.14
- zigdouble
中,你会失去永远无法恢复的信息。将其升级为extended
也没有帮助。尝试d := 2.135; e := 2.135; Writeln(extended(d) = e);
。 - Andreas Rejbrand1E-15
特别适用于double
。 - Andreas RejbrandAValue
恰好位于具有指定十的幂的两个最接近的值之间(上方和下方),则此函数返回:
如果AValue
为正,则朝正无穷大的值。
如果AValue
为负且FPU舍入模式未设置为rmUp,则向负无穷方向的值。
TRoundToRange
,它是指数(10的幂),而不是.NET的Math.Round方法中的小数位数。因此,要将数字四舍五入到小数点后2位,您需要将其作为round-to范围使用-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