改进delphi中的RoundTo函数

    delphi 7中自带数值四舍五入函数RoundTo(AVlaue, ADigit)存在一些不确定性情况,并非像帮助或者网络说的四舍六入五凑偶的规则,不信可以随便测试几个数据就会发现与你预期的不一样,比如33.015与33.035,修约2位小数,运行结果却是33.01与33.03。这主要是与浮点数的精度有关(有兴趣可以了解一下浮点数的存储结构,我之前有转载了一篇相关文章),我改进这个问题,较好的解决的前面的问题,同时执行速度较快,用法与RoundTo一样,代码如下:

function IsVeryNear1(f: double): boolean;
var    // 判断给定实数的小数部分是否无限接近1,根据浮点数的存储格式来判定
  f1: double;
  zs, i:integer;
  arr: array [1..8] of byte;
  pb: Pbyte;
  pfInt: Pint64;
  fInt, tmp1, tmp2:int64;
  p: Pointer;
begin
  p := @f;
  pb := Pbyte(p);
  for i := 1 to 8 do
  begin
    arr[9 - i] := pb^;
    inc(pb);
  end;
  zs := ((arr[1] and $7f) shl 4) + ((arr[2] and $F0) shr 4) - 1023; //浮点数的指数
  if zs < -1 then   // 小数部分前几位全是零的情况
  begin
    result := false;
    Exit;
  end;
  pfInt := PInt64(p);
  fInt := pfInt^;
  fInt := ((fInt and $000fffffffffffff) or $0010000000000000);
  if (zs = -1) then
  begin
    if fInt = $001fffffffffffff then result := true
    else result := false;
  end
  else begin
    tmp1 := $000fffffffffffff;
    tmp2 := $001fffffffffffff;
    for i := 0 to zs do
    begin
      tmp2 := (tmp2 and tmp1);
      tmp1 := (tmp1 shr 1);
    end;
    if ((fInt and tmp2) = tmp2) then  result := true // 当小数部分全部为1时,理解为小数无限接近1
    else result := false;
  end;
end;
// 新的改进型四舍五入函数
function NewRoundTo(const AValue: double; const ADigit: TRoundToRange): Double;
var
  ef, f1, a2:  double;
  i, n: integer;
  a1, intV: int64;
  f_sign: boolean;
begin
  if AValue = 0 then begin
    Result := 0;
    Exit;
  end;
  if ADigit < 0 then // 修约小数点之后的小数位
  begin
    if AValue > 0 then f_sign := true  // 正数
    else f_sign := false;              // 负数
    a1 := 1;
    for i := 1 to (-ADigit) do a1 := a1 * 10;
    ef := abs(AValue * a1 * 10);
    intV := trunc(ef);
    if isVeryNear1(ef) then inc(intV);  // 这一步是关键
    n := (intV mod 10);
    if (n > 4) then  intV := intV - n + 10
    else intV := intV - n;
    if f_sign then  ef := intV/(a1*10)
    else ef := -1.0*intV/(a1*10);
    result := ef;
    exit;
  end;
  if ADigit = 0 then
  begin
    if frac(AValue) >= 0.5 then ef := trunc(AValue) + 1
    else ef := trunc(AValue);
    result := ef;
    exit;
  end;
  if ADigit > 0 then
  begin
    result := roundTo(AValue, ADigit);
    exit;
  end;
end;

这里还有另外一个他人写的解决函数,但是执行速度比前面的函数慢了非常多,只针对小数进行了修约,如下:

function RoundFloat(f: double; i: integer): double;
var
  s: string;
  ef: Extended;
begin
  if f = 0 then begin
    Result := 0;
    Exit;
  end;
  s := '#.' + StringOfChar('0', i);
  if s = '#.' then s := '#';
  ef := StrToFloat(FloatToStr(f)); //防止浮点运算的误差
  result := StrToFloat(FormatFloat(s, ef));
end;

 

 

posted on 2012-11-30 20:46  唐朝t  阅读(7711)  评论(0编辑  收藏  举报