改進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;

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM