線上訂房服務-台灣趴趴狗聯合訂房中心
發文 回覆 瀏覽次數:2214
推到 Plurk!
推到 Facebook!

如何避免浮點數的計算影響

答題得分者是:supman
bestlong
站務副站長


發表:126
回覆:734
積分:512
註冊:2002-10-19

發送簡訊給我
#1 引用回覆 回覆 發表時間:2005-06-28 10:00:02 IP:211.22.xxx.xxx 未訂閱
以下程式碼單純是處理 44.48 * 600 的但是對其結果使用 Frac() 函數來取出小數點部份卻發生不同的狀況. 基本上不適 Frac() 函數的問題, 而是在 TQuery 的浮點計算產生的誤差, 用 Delphi 的單步追蹤 T1, T2, T3 都會得到 26688 結果, 但是取小數部分後 R1 為 0 這是正確的,不過 R2, R3 卻變成 1 就不對了.    這造成我的程式有不可預期的錯誤狀況會發生. 不知有何改進方法?    
procedure TForm1.FormShow(Sender: TObject);
var
  T1, P1, Q1, R1:Extended;
  T2, P2, Q2, R2:Extended;
  T3, P3, Q3, R3:Extended;
begin
// 使用 Function Frac() 來對資料庫所讀取的數值取小數部份, 會出現的計算錯誤的問題      memo1.Clear;      R1 := 0;
  P1 := 44.48;
  Q1 := 600;
  T1 := (P1 * Q1);
  R1 := Frac(T1); // 此處計算得到 0
  memo1.Lines.Add(FloatToStr(R1));      if qy_m.Active then
    qy_m.Close;
  qy_m.SQL.Text := 'select 44.48 as P, 600 as Q from parts';
  qy_m.Open;      R3 := 0;
  T3 := 0;
  P3 := qy_m.FieldByName('P').Value;
  Q3 := qy_m.FieldByName('Q').Value;
  T3 := (P3 * Q3);
  R3 := Frac(T3); // 此處計算得到 0.999999999998124 但是會四捨六入得到 1
  memo1.Lines.Add(FLoatToStr(R3));      R2 := 0;
  P2 := qy_m.FieldByName('P').AsFloat;
  Q2 := qy_m.FieldByName('Q').AsFloat;
  T2 := (P2 * Q2);
  R2 := Frac(T2);// 此處計算得到 0.999999999998124 但是會四捨六入得到 1
  memo1.Lines.Add(FloatToStr(R2));
end;
雪龍 http://bestlong.no-ip.com/ 學海無涯覺無盡,勤做筆記防失憶
------
http://blog.bestlong.idv.tw/
http://www.bestlong.idv.tw/
http://delphi-ktop.bestlong.idv.tw/
supman
尊榮會員


發表:29
回覆:770
積分:924
註冊:2002-04-22

發送簡訊給我
#2 引用回覆 回覆 發表時間:2005-06-28 10:37:42 IP:61.70.xxx.xxx 未訂閱
您好: 我做了以下測試,不會出現您所說出現"1"的狀況 procedure TForm1.Button2Click(Sender: TObject); var E,R:Extended; begin E:=0.999999999998124; R:=Frac(E); Memo1.Lines.Add(FloatToStr(R)) end; 不曉得是不是您在 T3 := (P3 * Q3); 這時候就出了問題? 您P3,Q3值是多少?,資料庫中宣告成什麼欄位?
bestlong
站務副站長


發表:126
回覆:734
積分:512
註冊:2002-10-19

發送簡訊給我
#3 引用回覆 回覆 發表時間:2005-06-28 12:54:57 IP:211.22.xxx.xxx 未訂閱
這就是我的範例程式用三段計算來測試問題點, 第一段用變數賦值直接計算是沒有問題的 而第二段與第三段都是讀取 TQuery 的欄位值來計算就產生問題. 而 SQL 是動態指定的所以不會有設定 TField 的情形. 原來的程式已經上線多年, 是因為我接受後在專案編譯參數開啟 Range Checking 的項目, 之後也經過很久才發現此一存在已久問題. 資料庫對應的欄位型態 P => Numeric (8.3) Q => int 我想這可能資料形態轉換的問題. 雪龍 http://bestlong.no-ip.com/ 學海無涯覺無盡,勤做筆記防失憶 發表人 - bestlong 於 2005/06/28 12:59:18
------
http://blog.bestlong.idv.tw/
http://www.bestlong.idv.tw/
http://delphi-ktop.bestlong.idv.tw/
bestlong
站務副站長


發表:126
回覆:734
積分:512
註冊:2002-10-19

發送簡訊給我
#4 引用回覆 回覆 發表時間:2005-06-28 13:06:20 IP:211.22.xxx.xxx 未訂閱
另外程式碼中就算是將 Q3 := qy_m.FieldByName('Q').Value; 改成 Q3 := qy_m.FieldByName('Q').AsInteger; Q2 := qy_m.FieldByName('Q').AsFloat; 改成 Q2 := qy_m.FieldByName('Q').AsInteger; 結果也是一樣. 我是將變數都透過 Debug 的 Watch List 中觀察的狀況. 其中 R2 與 R3 計算得到 0.999999999998124 的結果就是已經是錯誤解答 44.48 * 600 = 26688 對 26688 取小數部份應該是 0.00 才對不應該是 0.999999999998124 雪龍 http://bestlong.no-ip.com/ 學海無涯覺無盡,勤做筆記防失憶 發表人 - bestlong 於 2005/06/28 13:08:40
------
http://blog.bestlong.idv.tw/
http://www.bestlong.idv.tw/
http://delphi-ktop.bestlong.idv.tw/
supman
尊榮會員


發表:29
回覆:770
積分:924
註冊:2002-04-22

發送簡訊給我
#5 引用回覆 回覆 發表時間:2005-06-28 13:46:48 IP:61.70.xxx.xxx 未訂閱
您好: 我使用AdoQuery,然後在MS SQL中建立了您設定的欄位,再把Project的Range Checking打開,然後用以下程式碼 procedure TForm1.Button3Click(Sender: TObject); var E,R:Extended; P,Q:Extended; begin P:=AdoQuery1.FieldByName('a').Value; Q:=AdoQuery1.FieldByName('b').Value; E:=P*Q; R:=Frac(E); Memo1.Lines.Add(FloatToStr(R)) end; 得到的答案是正常的0. 實際測試Range Checking打開不打開結果都是0. 也許是DataSet的問題,如果您不是使用Ado可以換成Ado試看看. 發表人 - supman 於 2005/06/28 13:50:50
bestlong
站務副站長


發表:126
回覆:734
積分:512
註冊:2002-10-19

發送簡訊給我
#6 引用回覆 回覆 發表時間:2005-06-28 15:59:44 IP:211.22.xxx.xxx 未訂閱
先感謝 supman 幫忙測試, 我的環境是 BDE MSSQL 6.5 因為現行程式系統有很多的歷史包袱, 改版 ADO 的計畫等於要將整個系統重寫, 不過也陸續在作架構的測試. 畢竟 BDE 與 ADO 的進階使用還是有很大的差異, 特別是在 CacheUpdate 的模式. 不過, 我會再對此一問題做更詳細的測試. 總是會找到問題點的. 雪龍 http://bestlong.no-ip.com/ 學海無涯覺無盡,勤做筆記防失憶
------
http://blog.bestlong.idv.tw/
http://www.bestlong.idv.tw/
http://delphi-ktop.bestlong.idv.tw/
supman
尊榮會員


發表:29
回覆:770
積分:924
註冊:2002-04-22

發送簡訊給我
#7 引用回覆 回覆 發表時間:2005-06-28 16:32:55 IP:61.70.xxx.xxx 未訂閱
如果沒記錯MS SQL 6.5在MS還沒買下DB2資料庫引擎的時候,問題很多,跟SQL7有天壤之別,經常只能避開問題,而無法真正的解決問題,所以如果是這樣的架構的話,可能只能用其他方式去避開他的問題了. 以下跟這篇問題沒太大關係,不過提供您參考一下. http://delphi.ktop.com.tw/topic.php?TOPIC_ID=73201
bestlong
站務副站長


發表:126
回覆:734
積分:512
註冊:2002-10-19

發送簡訊給我
#8 引用回覆 回覆 發表時間:2005-06-28 17:01:56 IP:211.22.xxx.xxx 未訂閱
目前用程式來寫也確實在資料庫上開一個 Test 資料表    BDE 連接資料庫屬性 ENABLE BCD=TRUE     Part_qty 是用 TIntegerField Part_prc 是用 TBCDField    
procedure T_fmTest.qy_mCalcFields(DataSet: TDataSet);
var
  Q1,P1:Extended;
begin
  inherited;
//Q1 := qy_m.FieldByName('Part_qty').Value;
//P1 := qy_m.FieldByName('Part_prc').Value;
  Q1 := qy_m.FieldByName('Part_qty').AsFloat;
  P1 := qy_m.FieldByName('Part_prc').AsFloat;
  qy_m.FieldByName('t1').Value := Q1 * P1;
  qy_m.FieldByName('t2').Value := Frac(Q1 * P1);
end;
執行結果如圖中紅色框框的就是計算有問題的部份 若是 Q1, P1 用 .Value 的方式計算就會正常. 雪龍 http://bestlong.no-ip.com/ 學海無涯覺無盡,勤做筆記防失憶 發表人 - bestlong 於 2005/06/28 17:07:27
------
http://blog.bestlong.idv.tw/
http://www.bestlong.idv.tw/
http://delphi-ktop.bestlong.idv.tw/
bestlong
站務副站長


發表:126
回覆:734
積分:512
註冊:2002-10-19

發送簡訊給我
#9 引用回覆 回覆 發表時間:2005-06-29 10:25:10 IP:211.22.xxx.xxx 未訂閱
不知道 supman 有沒有將變數型態改成 float 來測試. 目前我的環境下歸納出來 若是變數是用 float 型態然後取資料欄位無論是用 AsFloat 或是 Value 都會出問題. 若是變數用 Extended 型態用 AsFloat 會出問題而用 Value 就暫時正常. 雪龍 http://bestlong.no-ip.com/ 學海無涯覺無盡,勤做筆記防失憶 發表人 - bestlong 於 2005/06/29 10:26:14
------
http://blog.bestlong.idv.tw/
http://www.bestlong.idv.tw/
http://delphi-ktop.bestlong.idv.tw/
supman
尊榮會員


發表:29
回覆:770
積分:924
註冊:2002-04-22

發送簡訊給我
#10 引用回覆 回覆 發表時間:2005-06-29 11:59:45 IP:61.70.xxx.xxx 未訂閱
我想這應該是浮點數的精密度問題,另外Delphi並沒有Float型態.    測試程式
procedure TForm1.Button3Click(Sender: TObject);
var
 E,R:Extended;
 P,Q:Extended;
 P1,Q1:Comp;
 E1,R1:Comp;
begin
P:=AdoQuery1.FieldByName('a').Value;
Q:=AdoQuery1.FieldByName('b').Value;
E:=P*Q;
R:=Frac(E);
Memo1.Lines.Add(FloatToStr(R));
P:=AdoQuery1.FieldByName('a').AsFloat;
Q:=AdoQuery1.FieldByName('b').AsFloat;
E:=P*Q;
R:=Frac(E);
Memo1.Lines.Add(FloatToStr(R));
P1:=AdoQuery1.FieldByName('a').AsFloat;
Q1:=AdoQuery1.FieldByName('b').AsFloat;
E1:=P1*Q1;
R1:=Frac(E1);
Memo1.Lines.Add(FloatToStr(R1));
end;    Type Spec:    Type        Range        Significant digits        Size in bytes
Real48        2.9 x 10^?9 .. 1.7 x 10^38        11?2        6
Single        1.5 x 10^?5 .. 3.4 x 10^38        7?        4
Double        5.0 x 10^?24 .. 1.7 x 10^308        15?6        8
Extended        3.6 x 10^?951 .. 1.1 x 10^4932        19?0        10
Comp        ?^63 1 .. 2^63 ?        19?0        8
Currency        ?22337203685477.5808.. 922337203685477.5807        19?0        8
The generic type Real, in its current implementation, is equivalent to Double.    Type        Range        Significant digits        Size in bytes
Real        5.0 x 10^?24 .. 1.7 x 10^308        15?6        
使用Value: 更動 P1,Q1:Comp;的型態定義可以發現,精密度越低則誤差越大,測試結果是當精密度大於Extended時就正常. 使用AsFloat: 精密度必須到Comp以上才會正常. 有一點我也很納悶,不管我是用Value,或是AsFloat,當指定給變數後照理講應該要跟著那個變數型態走,但事實上,如果是使用AsFloat,宣告為Extended也會失效,必須要再將變數精密度提升到Comp才正常,但使用Watch去看,卻看不出來宣告為Extended與Comp的變數值有何差異. 發表人 - supman 於 2005/06/29 12:08:44
bestlong
站務副站長


發表:126
回覆:734
積分:512
註冊:2002-10-19

發送簡訊給我
#11 引用回覆 回覆 發表時間:2005-06-29 15:45:05 IP:211.22.xxx.xxx 未訂閱
更正一下手誤不是 Float 是 Double 型態 不過照這樣看來 Watch List 所觀察的數值, 應該也是經過轉型的結果. 雪龍 http://bestlong.no-ip.com/ 學海無涯覺無盡,勤做筆記防失憶
------
http://blog.bestlong.idv.tw/
http://www.bestlong.idv.tw/
http://delphi-ktop.bestlong.idv.tw/
系統時間:2024-11-22 10:16:01
聯絡我們 | Delphi K.Top討論版
本站聲明
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。
2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。
3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇!