如何避免浮點數的計算影響 |
答題得分者是:supman
|
bestlong
站務副站長 發表:126 回覆:734 積分:512 註冊:2002-10-19 發送簡訊給我 |
以下程式碼單純是處理 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 發送簡訊給我 |
|
bestlong
站務副站長 發表:126 回覆:734 積分:512 註冊:2002-10-19 發送簡訊給我 |
這就是我的範例程式用三段計算來測試問題點, 第一段用變數賦值直接計算是沒有問題的
而第二段與第三段都是讀取 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 發送簡訊給我 |
另外程式碼中就算是將 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 發送簡訊給我 |
您好:
我使用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 發送簡訊給我 |
先感謝 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 發送簡訊給我 |
|
bestlong
站務副站長 發表:126 回覆:734 積分:512 註冊:2002-10-19 發送簡訊給我 |
目前用程式來寫也確實在資料庫上開一個 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 發送簡訊給我 |
不知道 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 發送簡訊給我 |
我想這應該是浮點數的精密度問題,另外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 發送簡訊給我 |
本站聲明 |
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。 2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。 3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇! |