四舍五入问题 ( 积分: 50 )

  • 主题发起人 主题发起人 snowmao
  • 开始时间 开始时间
S

snowmao

Unregistered / Unconfirmed
GUEST, unregistred user!
Dephi7出现如下问题
FormatFloat('0.##', 72.657)显示72.68,正确
n := 72.657;
FormatFloat('0.##', n)显示72.67,n为Double型,错误
为什么?
 
FormatFloat('##0.00', 72.657)
 
http://www.delphibbs.com/keylife/iblog_show.asp?xid=27702

看看我的笔记吧
 
kk2000:这是什么道理呢
 
在Delphi中使用四舍五入函数一直是使用Round,可是有时候发现,使用它得到的答案与我们预期的会不太一样。

举例:
i := Round(11.5) 结果: i=12
i := Round(10.5) 结果: i=10

是的,按照我们的预期,第二个函数应该返回11才对,可是,为什么会这样呢?
对于XXX.5的情况,整数部分是奇数,那么会Round Up,偶数会Round Down。难道是Delphi的bug?

No!! 让我们看看<<Pascal精要>>上的一句话:
"在最近版本的Delphi Pascal 编译器中,Round 函数是以 CPU 的 FPU (浮点部件) 处理器为基础的。这种处理器采用了所谓的 "银行家舍入法",即对中间值 (如 5.5、6.5) 实施Round函数时,处理器根据小数点前数字的奇、偶性来确定舍入与否,如 5.5 Round 结果为 6,而 6.5 Round 结果也为6, 因为 6 是偶数"。

Round函数其实使用的银行家算法进行运算的,统计学上一般也是使用这种算法的,这比我们传统的四舍五入方法要科学,可是,如果我们要使用传统的四舍五入的方法,该如何解决呢?

有人是这样解决的,给10.5加上一个很微小的数值,再调用Round函数,这样在不影响精度的同时,就得到了正确的结果,貌似不错,可这始终是治标不治本的方法,有没有更正统的解决方法呢?

在网上又搜到了一个函数:
function DoRound(Value: Extended): Int64;
procedure Set8087CW(NewCW: Word);
asm
MOV Default8087CW,AX
FNCLEX
FLDCW Default8087CW
end;
const
RoundUpCW = $1B32;
var
OldCW : Word;
begin
OldCW := Default8087CW;
try
Set8087CW(RoundUpCW);
Result := Round(Value);
finally
Set8087CW(OldCW);
end;
end;

先解释一下8087CW, 全称是8087 control word。它是CPU中浮点单元(FPU)控制器控制字的值,设置8087CW,会改变FPU的精度,舍入模式,以及运算出错时是否产生异常。
上面程序的思路很简单,就是先保存8087CW,然后设置它为Round Up,这样偶数时就不会Round Down了,最后再还原8087CW。

其实上面的函数还可以简化,因为System单元里已经提供了Set8087CW的实现,所以程序简化为
function DoRound(Value: Extended): Int64;
const
RoundUpCW = $1B32;
var
OldCW : Word;
begin
OldCW := Default8087CW;
try
Set8087CW(RoundUpCW);
Result := Round(Value);
finally
Set8087CW(OldCW);
end;
end;

到这里为止,这篇文章可以告一段落了,可是,经过摸索,我发现另一种相似而有趣的解决方案。
其实Borland早就想到我们会遇到这样的问题,想到我们需要定制FPU的舍入模式,所以它提供了现成的函数供我们使用。在Math单元里,有一个SetRoundMode函数。下面是我封装的一个四舍五入函数:
function RoundEx(Value: Extended; RoundMode: TFPURoundingMode = rmUp): Int64;
var
RM: TFPURoundingMode;
begin
RM := GetRoundMode;
try
SetRoundMode(RoundMode);
Result := Round(Value);
finally
SetRoundMode(RM);
end;
end;

举例:
i := RoundEx(11.5) 结果: i=12
i := RoundEx(10.5) 结果: i=11
嗯,这样对了吧,如果我设置成其它RoundMode会怎样呢?

举例:
i := RoundEx(11.5, rmTruncate) 结果: i=11
i := RoundEx(10.5, rmTruncate) 结果: i=10

RoundEx函数华丽的变身为Trunc函数了,是不是很有趣啊,哈哈!
 
多人接受答案了。
 
后退
顶部