bestlong 怕失憶論壇

標題: Round 函數不正確的四捨五入狀況 [打印本頁]

作者: bestlong    時間: 2007-3-7 10:49     標題: Round 函數不正確的四捨五入狀況

  1. Delphi 的 Round() 四捨五入函數採奇入偶捨方式, Ex:

  2.   Round(1.5) => 2
  3.   Round(2.5) => 2
  4.   Round(3.5) => 4

  5. 2.5 得到的結果怎麼會是 2 呢?

  6. 解決之道: 自己寫四捨五入函數, 以下提供兩種:

  7.   方法一:

  8.     function MyRound(P: Double; Decimals: integer): Double;
  9.     var
  10.       factor: LongInt;
  11.       help: Double;
  12.       i: integer;
  13.     begin
  14.       factor := 1;
  15.       for i := 1 to decimals do
  16.         factor := factor * 10;
  17.   
  18.       if P < 0 then
  19.         help := -0.5
  20.       else
  21.         help := 0.5;
  22.       Result := Int(P*factor+help) / factor;
  23.       if (Result > -0.00000001) and (Result < 0.00000001) then
  24.         Result := 0.00;
  25.     end;

  26.   方法二: 利用 FloatToStrF 及 StrToFloat 函數:
  27.    
  28.     function MyRound(P: Double; Decimals: integer): Double;
  29.     var
  30.       s: string;
  31.     begin
  32.       s := FloatToStrF(P, ffFixed, 15, Decimals)
  33.       Result := StrToFloat(s);
  34.     end;
複製代碼

作者: bestlong    時間: 2007-4-4 15:22

在Delphi中使用Round函數得到的答案有時與我們所預期的會不太一樣:

採用的是四捨六入五留雙。即當捨去或進位大於或小於五時按四捨五入來處理,而當捨去或進位等於五時,就要看前面一位是什麼,根據根据奇進偶不進,它總是返回一個偶數值。

     範例             結果
i:= Round(11.5)        12
i:= Round(10.5)        10

這種Round其實是按照銀行家演算法,統計學上一般都用這種算法,比傳統的"四捨五入"要科學。
如果要使用傳統的"四捨五入"方法,可以使用下面函數:
  1. function RoundClassic(R: Real): Int64;
  2. begin
  3.   Result:= Trunc(R);
  4.   if Frac(R) >= 0.5 then
  5.     Result := Result + 1;
  6. end;
複製代碼
其實在VB、Excel、.NET相關的語言中都有這個問題。
作者: bestlong    時間: 2009-12-17 09:16

從 Delphi 6 開始可以使用 SimpleRoundTo() 函數來處理四捨五入,而 Delphi 5 環境就需要自己補上,不過可以參考 D6 或 D7 的程式碼。
作者: bestlong    時間: 2010-5-26 20:56

四捨五入喔,不要再忘記了~

From http://jasper-dale.spaces.live.c ... E92E2726C!470.entry

每次要用到 四捨五入 都忘記是那個指令!

function Round(X: Extended): Int64;

If X is exactly halfway between two whole numbers, the result is always the even number.
如果X剛好在兩數的中間,那回傳的數將會是 "偶數" 的那一個。
This method of rounding is often called "Banker's Rounding".
我才不管什麼 "Banker's Rounding" ,實用最重要。
在網路上找到很多資訊,加以小改一下成為台灣適用的『四捨五入』
就呼叫自己的 RoundF 吧!
  1. function RoundF(X: Extended; Decimal: integer = 0): Extended;
  2. var
  3.   PowerNum: Extended;
  4. begin
  5.   PowerNum := IntPower(10, Decimal);
  6.   Result := RoundI(X * PowerNum) / PowerNum;
  7. end;

  8. function RoundI(X: Extended): Int64;
  9. begin
  10.   if X < 0 then
  11.     Result := Round(X - 0.0000001)
  12.   else
  13.     Result := Round(X + 0.0000001);
  14. end;
複製代碼

作者: bestlong    時間: 2011-3-8 11:49

參考樓上的修改成這樣
  1. function RoundF(X: Extended; Decimal: integer = 2): Extended;
  2. var
  3.   PowerNum: Extended;
  4. begin
  5.   PowerNum := IntPower(10, Decimal);
  6.   if X < 0 then
  7.     Result := Round((X * PowerNum) - 0.1) / PowerNum
  8.   else
  9.     Result := Round((X * PowerNum) + 0.1) / PowerNum;
  10. end;
複製代碼
測試程式
  1. unit Unit1;

  2. interface

  3. uses
  4.   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  5.   StdCtrls, Math;

  6. type
  7.   TForm1 = class(TForm)
  8.     Memo1: TMemo;
  9.     procedure FormCreate(Sender: TObject);
  10.   private
  11.     { Private declarations }
  12.   public
  13.     { Public declarations }
  14.   end;

  15. var
  16.   Form1: TForm1;

  17. implementation

  18. {$R *.DFM}

  19. function RoundF(X: Extended; Decimal: integer = 2): Extended;
  20. var
  21.   PowerNum: Extended;
  22. begin
  23.   PowerNum := IntPower(10, Decimal);
  24.   if X < 0 then
  25.     Result := Round((X * PowerNum) - 0.1) / PowerNum
  26.   else
  27.     Result := Round((X * PowerNum) + 0.1) / PowerNum;
  28. end;

  29. procedure TForm1.FormCreate(Sender: TObject);
  30. var
  31.   f: Double;
  32. begin
  33.   Memo1.Clear;
  34.   f := 1213.245;
  35.   Memo1.Lines.Append(floattostr(f));
  36.   Memo1.Lines.Append(FloatToStr(RoundF(1213.245, 2)));
  37.   Memo1.Lines.Append(FloatToStr(RoundF(f, 2)));
  38.   Memo1.Lines.Append('');
  39.   f := -1213.245;
  40.   Memo1.Lines.Append(floattostr(f));
  41.   Memo1.Lines.Append(FloatToStr(RoundF(-1213.245, 2)));
  42.   Memo1.Lines.Append(FloatToStr(RoundF(f, 2)));
  43. end;

  44. end.
複製代碼
執行結果
四捨五入測試.png

圖片附件: 四捨五入測試.png (2011-3-8 11:57, 8.31 KB) / 下載次數 146
http://www.bestlong.idv.tw/forum.php?mod=attachment&aid=MjQ4fDRiMjYwMzdjfDE3MTQ3ODE5ODd8MA%3D%3D


作者: bestlong    時間: 2011-3-8 12:01

這是在 Delphi.KTop 新出現與四捨五入相關的問題, 發現用 1213.245 值先放到 Double 型態的變數內然後再傳入 SimpleRoundTo() 還是會發生四捨六入五成雙的狀況, 所以也是自行寫函數處理, 程式碼如下:
  1. function ArithmeticRoundTo(Value : double; Digit : integer) : double;
  2. var txt : string;
  3. begin
  4.   Digit := Abs(Digit); //不接受負數
  5.   txt := FormatFloat('#0.' + DupeString('0', Digit+1), Value); //例如 2 位時,用第 3 位來判斷,所以 Digit + 1
  6.   if txt[Length(txt)] = '5' then //最後一位遇 5 就變 9,則 SimpleRoundTo()一定會進位
  7.     txt[Length(txt)] := '9';
  8.   Result := SimpleRoundTo(StrToFloat(txt), -Digit);
  9. end;
複製代碼
不過這樣用格式化字串再做識別的方法, 在執行效能上應該會有較多的負擔.

資料來源 http://delphi.ktop.com.tw/board. ... d=69&tid=102040
作者: bestlong    時間: 2011-7-13 08:30

參考來源 http://d.02t.cn/html/jczs/1188.html

DELPHI四舍五入问题解决

这段时间在用 DELPHI 做一个财务系统时发现每一行的小计取了两位小数后与用SQL的ROUND查询出来的不一样,在程序中是用 FormatFloat('0.00', ItemSum) 函数来取值的,再用 DXDBGRID 网格显视合计,最终与 SELECT SUM(ROUND(ITEMSUM,2)) 得出的结果是不一样的。

例如 ItemSum := 1.005,在程序 FormatFloat('0.00', ItemSum) 就返回是“1”,应该是“1.01”才是。

因为以前做的系统都没有用 DXDBGIRD 来显视合计,一直都没留意这个问题,然后新建了一个程序来测试,用 FormatFloat('0.00', 1.005) 得出是“1.01”是正确的,这是怎么回事呢,将 1.005 付给变量 ItemSum 再 FormatFloat('0.00', ItemSum)就返回是“1”,单步调试时 ItemSum 一直都是 1.005,通过变量同一个数据就有不同的结果。

一开始 ItemSum 是 Double 类型的,后来改为 Extended 后 FormatFloat('0.00', ItemSum) 就返回是“1.01”了,为什么会这样呢,后来想了一下可能是 Double 类型,因为在计算机中数是用二进制存储的,而十进制的有限小数转换成二进制可能变成无限小数,所以存储的时候会有一定的误差。在通常情况下,如果要比较两个数的大小,必须通过对它们的求差得到,如比较 ItemSum1 与 ItemSum 是否相等,应用 abs(ItemSum1 - ItemSum1) < 0 来判断。1.005 就变成了 1.004999999999999....了。

使用 Extended 或者 FormatFloat('0.00', StrToFloat(FlotToStr(ItemSum))) 就解决问题了。

**************************

在 DELPHI 中 Round() 和 RoundTo() 都是四舍五入的函数,但 DELPHI 用的四舍五入与一般的四舍五入不同,它采用的是四舍六入五留双。

即当舍或入位大于或小于五时按四舍五入来处理,而当舍或入位等于五时,就要看前面一位是什么,根据奇进偶不进,它总是返回一个偶数值。

这种算法其实是按照银行家算法,统计学上一般都用这种算法,比传统的"四舍五入"要科学。在 VB、.NET 相关的语言中都有这个问题。

例如:

表达式 返回值

Round(11.5) 12

Round(10.5) 10

RoundTo(1234567, 3) 1234000

RoundTo(1.234, -2) 1.23

RoundTo(1.235, -2) 1.24

RoundTo(1.245, -2) 1.24

如果要使用传统的"四舍五入"方法,可以使用下面函数:
  1. function RoundEx(R: Real): Int64;
  2. begin
  3.   Result := Trunc(R);
  4.   if Frac(R) >= 0.5 then
  5.     Result := Result + 1;
  6.   end;

  7. function DRound(const Value: Extended; const Digit: Byte = 0): Extended;
  8. var
  9.   tmp: Extended;

  10. begin
  11.   tmp := Power(10, Digit);
  12.   if Value > 0 then
  13.     Result := Value * tmp + 0.5
  14.   else
  15.     Result := Value * tmp - 0.5;
  16.   Result := Trunc(Result) / tmp;
  17. end;
複製代碼

作者: bestlong    時間: 2012-11-12 14:38

  1. function RoundF(const Value: Extended; const Digit: Byte = 0): Extended;
  2. var
  3.   tmp: Extended;
  4. begin
  5.   tmp := Power(10, Digit);
  6.   if Value > 0 then
  7.     Result := Value * tmp + 0.5
  8.   else
  9.     Result := Value * tmp - 0.5;
  10.   Result := Trunc(Result) / tmp;
  11. end;

  12. procedure TForm1.FormCreate(Sender: TObject);
  13. var
  14. //  f1, f2: Double;
  15.   f1, f2: Extended;
  16. begin
  17.   Memo1.Clear;
  18.   f1 := 1213.235;
  19.   f2 := 1213.245;
  20.   Memo1.Lines.Append(FloatToStr(f1));
  21.   Memo1.Lines.Append(FloatToStr(f2));
  22.   Memo1.Lines.Append('1213.235 => ' + FloatToStr(RoundF(1213.235, 2)));
  23.   Memo1.Lines.Append('1213.245 => ' + FloatToStr(RoundF(1213.245, 2)));
  24.   Memo1.Lines.Append(FloatToStr(RoundF(f1, 2)));
  25.   Memo1.Lines.Append(FloatToStr(RoundF(f2, 2)));
  26.   Memo1.Lines.Append('');
  27.   f1 := -1213.235;
  28.   f2 := -1213.245;
  29.   Memo1.Lines.Append(FloatToStr(f1));
  30.   Memo1.Lines.Append(FloatToStr(f2));
  31.   Memo1.Lines.Append('-1213.235 => ' + FloatToStr(RoundF(-1213.235, 2)));
  32.   Memo1.Lines.Append('-1213.245 => ' + FloatToStr(RoundF(-1213.245, 2)));
  33.   Memo1.Lines.Append(FloatToStr(RoundF(f1, 2)));
  34.   Memo1.Lines.Append(FloatToStr(RoundF(f2, 2)));
  35.   Memo1.Lines.Append('');
  36.   f1 := 1213.5;
  37.   f2 := 1214.5;
  38.   Memo1.Lines.Append(FloatToStr(f1));
  39.   Memo1.Lines.Append(FloatToStr(f2));
  40.   Memo1.Lines.Append('1213.5 => ' + FloatToStr(RoundF(1213.5)));
  41.   Memo1.Lines.Append('1214.5 => ' + FloatToStr(RoundF(1214.5)));
  42.   Memo1.Lines.Append('1213.5 => ' + FloatToStr(RoundF(f1)));
  43.   Memo1.Lines.Append('1214.5 => ' + FloatToStr(RoundF(f2)));
  44.   Memo1.Lines.Append('1213.5 => ' + FloatToStr(Round(1213.5)));
  45.   Memo1.Lines.Append('1214.5 => ' + FloatToStr(Round(1214.5)));
  46.   Memo1.Lines.Append('1213.5 => ' + FloatToStr(Round(f1)));
  47.   Memo1.Lines.Append('1214.5 => ' + FloatToStr(Round(f2)));
  48. end;
複製代碼
在傳入值 f1 與 f2 使用 Extended 型態得到下圖的結果:
arg-use-extended-type.jpg

而傳入值 f1 與 f2 使用 Double 型態得到下圖的結果:
arg-use-double-type.jpg

要注意當使用 Double 型態與 Extended 型態之間互傳後,數值就會有差異

圖片附件: arg-use-extended-type.jpg (2012-11-12 14:45, 21.24 KB) / 下載次數 106
http://www.bestlong.idv.tw/forum.php?mod=attachment&aid=MzU3fDEzZDU5YTRifDE3MTQ3ODE5ODd8MA%3D%3D



圖片附件: arg-use-double-type.jpg (2012-11-12 14:45, 21.3 KB) / 下載次數 104
http://www.bestlong.idv.tw/forum.php?mod=attachment&aid=MzU4fGJlNjAxNzM4fDE3MTQ3ODE5ODd8MA%3D%3D






歡迎光臨 bestlong 怕失憶論壇 (http://www.bestlong.idv.tw/) Powered by Discuz! X1.5