全國最多中醫師線上諮詢網站-台灣中醫網
發文 回覆 瀏覽次數:9834
推到 Plurk!
推到 Facebook!

解決算術式四捨五入(SimpleRoundTo 有臭蟲)

 
cancer
高階會員


發表:58
回覆:319
積分:190
註冊:2004-07-31

發送簡訊給我
#1 引用回覆 回覆 發表時間:2011-01-13 19:59:31 IP:211.75.xxx.xxx 訂閱
各位好,今天下午搞  SimpleRoundTo() 搞了半天,原來它是有 Bug 的,用 FormatFloat() 一樣不行,
// 傳入立即值
SimpleRoundTo(1213.245, -2) // 傳回 1213.25,OK
FormatFloat('#0.00', 1213.245,) // 傳回 "1213.25",也OK

// 先放變數 f
var f : double;
f := 1213.245;
SimpleRoundTo(f, -2) // 傳回 1213.24,吐血了,明明都是 1213.245 跟上面一樣!
FormatFloat('#0.00', f) // 傳回 "1213.24",也吐血

最後只好自己寫一固函式來處理

uses Math;

function ArithmeticRoundTo(Value : double; Digit : integer) : double;
var txt : string;
begin
Digit := Abs(Digit); //不接受負數
txt := FormatFloat('#0.' DupeString('0', Digit 1), Value); // 例如 2 位時,用第 3 位來判斷,所以 Digit 1
if txt[Length(txt)] = '5' then // 最後一位遇 5 就變 9,則 SimpleRoundTo()一定會進位
txt[Length(txt)] := '9';
Result := SimpleRoundTo(StrToFloat(txt), -Digit);
end;
tobylin
一般會員


發表:1
回覆:15
積分:18
註冊:2009-12-25

發送簡訊給我
#2 引用回覆 回覆 發表時間:2011-01-13 20:31:36 IP:60.249.xxx.xxx 未訂閱
SimpleRoundTo應該是 『四捨六入五成雙』的方法來處理轉換的動作。

所謂的『四捨六入五成雙』簡單的說起來就是當數值小於5的話則捨棄小數(四捨)、大於5的話則進位(六入)、等於5的話則依最接近的雙數來決定是進位或捨去(五成雙)。
P.D.
版主


發表:603
回覆:4038
積分:3874
註冊:2006-10-31

發送簡訊給我
#3 引用回覆 回覆 發表時間:2011-01-14 00:38:09 IP:118.160.xxx.xxx 未訂閱
其實你去查formatfloat() 的code
function FormatFloat(const Format: string; Value: Extended): string;
var
Buffer: array[0..255] of Char;
begin
if Length(Format) > SizeOf(Buffer) - 32 then ConvertError(SFormatTooLong);
SetString(Result, Buffer, FloatToTextFmt(Buffer, Value, fvExtended,
PChar(Format)));
end;

發現了嗎?
f 應該宣告為 extended , 而不是 double, 改過來就ok了
2.另外, 如果 f 宣告為 double,
你試試看
f:= 1213.2451 及 f:= 1213.2450 所得到的結果?
我沒有很仔細去研究 double 的結構, 或許這當中 extended 轉換成 double 時已經是轉過一次了
編輯記錄
P.D. 重新編輯於 2011-01-13 09:38:58, 註解 無‧
cancer
高階會員


發表:58
回覆:319
積分:190
註冊:2004-07-31

發送簡訊給我
#4 引用回覆 回覆 發表時間:2011-01-14 09:34:30 IP:211.75.xxx.xxx 訂閱
FormatFloat() 是 Delphi 內建函式,我不想修改它,理由是:
1.重腦重灌要重裝 Delphi
2.程式可能會拿到更高版次的 Delphi 中重新編譯
3.程式可能會被其他同事拿來重新編譯
4.我可能把公司程式帶回家用筆記型電腦編譯
以上情形(尢其第 3 點),只要忘記修改 FormatFloat() 的函式定義 ,Build 出來程式,有錯查不出來,只能等客戶反應才會知道,有時候還被客戶取笑說 "連四捨五入那麻簡單的東西還會弄錯!"。

cancer
高階會員


發表:58
回覆:319
積分:190
註冊:2004-07-31

發送簡訊給我
#5 引用回覆 回覆 發表時間:2011-01-14 09:36:46 IP:211.75.xxx.xxx 訂閱

===================引 用 tobylin 文 章===================
SimpleRoundTo應該是 『四捨六入五成雙』的方法來處理轉換的動作。

所謂的『四捨六入五成雙』簡單的說起來就是當數值小於5的話則捨棄小數(四捨)、大於5的話則進位(六入)、等於5的話則依最接近的雙數來決定是進位或捨去(五成雙)。

您好,Round() 才是『四捨六入五成雙』,SimpleRoundTo() 是算術式四捨五入,遇五一定要進位。
cancer
高階會員


發表:58
回覆:319
積分:190
註冊:2004-07-31

發送簡訊給我
#6 引用回覆 回覆 發表時間:2011-01-14 09:41:23 IP:211.75.xxx.xxx 訂閱

===================引 用 P.D. 文 章===================
發現了嗎??
f 應該宣告為 extended , 而不是? double, 改過來就ok了
2.另外, 如果 f 宣告為 double,
你試試看
f:= 1213.2451 及 f:= 1213.2450 所得到的結果?
我沒有很仔細去研究 double 的結構, 或許這當中 extended 轉換成 double 時已經是轉過一次了

您好,我想是傳入 1213.245 結 SimpleRoundTo(),存入 extended 後變成 1213.24999 之類的,第三位由原本的 5 變成 4,所以才無法進位,但我最納悶的是傳入立即值 1213.245 卻可以進位,一樣是存入 extended,但又是正常的 。
P.D.
版主


發表:603
回覆:4038
積分:3874
註冊:2006-10-31

發送簡訊給我
#7 引用回覆 回覆 發表時間:2011-01-14 22:59:54 IP:118.160.xxx.xxx 未訂閱
看不懂唉! 沒有人要你改formatfloat()啊! 我提到的 formatfloat() 內建是宣告 extended 的值, 而不是double, 
所以當你宣告double 時, 其實透過 formatfloat 已經被轉過extended 了, 而這當中Delphi的轉換是否有進位問題就不知道了,
所以只要你 f 宣告為 extended 就可以達成 上述的結果!
這是我實測的結果
===================引 用 cancer 文 章===================
FormatFloat() 是 Delphi 內建函式,我不想修改它,理由是:
1.重腦重灌要重裝 Delphi
2.程式可能會拿到更高版次的 Delphi?中重新編譯
3.程式可能會被其他同事拿來重新編譯
4.我可能把公司程式帶回家用筆記型電腦編譯
以上情形(尢其第 3 點),只要忘記修改 FormatFloat() 的函式定義 ,Build 出來程式,有錯查不出來,只能等客戶反應才會知道,有時候還被客戶取笑說 "連四捨五入那麻簡單的東西還會弄錯!"。

cancer
高階會員


發表:58
回覆:319
積分:190
註冊:2004-07-31

發送簡訊給我
#8 引用回覆 回覆 發表時間:2011-01-15 23:35:29 IP:210.202.xxx.xxx 訂閱
版主好,我看一些國外網站,都是自己寫函來處理這個四捨五入的問題,如果變數宣告為 extended,他們不會多此一舉,還有資料庫程式的考量,我們都是用 DataSet.FieldByName('nw').AsFlaot 來讀取浮點數,AsFloat 是傳回 double 還是extended 變成關鍵了,如果傳回  double 。那麼,變數宣告成 extended 也沒用。
bestlong
站務副站長


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

發送簡訊給我
#9 引用回覆 回覆 發表時間:2011-03-08 12:02:50 IP:210.242.xxx.xxx 訂閱
------
http://blog.bestlong.idv.tw/
http://www.bestlong.idv.tw/
http://delphi-ktop.bestlong.idv.tw/
編輯記錄
GrandRURU 重新編輯於 2015-05-19 15:49:02, 註解 無‧
wiseinfo
一般會員


發表:3
回覆:4
積分:1
註冊:2006-07-13

發送簡訊給我
#10 引用回覆 回覆 發表時間:2012-01-04 23:22:08 IP:14.153.xxx.xxx 訂閱
看Math中的SimpleRoundTo有3个重载, 当传入参数是Double就有问题.
直接SimpleRoundTo(1213.245, -2) 会是OK的, 跟踪会进入Extended的重载,当Extended处理了.

我的解决办法, 这就是DELPHI折腾人的地方
function RoundEx(vValue: Extended; vScale: Integer): Extended;
var
EValue: Extended;
begin
EValue := StrToFloat(FloatToStr(vValue)); //这是必须的;FormatFloat也同样必须这样才正确
Result := SimpleRoundTo(EValue, vScale);
end;

StrToFloat返回的是Extended,应该一句可以搞定
Result := SimpleRoundTo(StrToFloat(FloatToStr(vValue)), vScale);


編輯記錄
wiseinfo 重新編輯於 2012-01-04 08:35:20, 註解 無‧
wiseinfo 重新編輯於 2012-01-04 09:04:05, 註解 無‧
GrandRURU
站務副站長


發表:240
回覆:1680
積分:1874
註冊:2005-06-21

發送簡訊給我
#11 引用回覆 回覆 發表時間:2015-05-19 15:37:47 IP:59.120.xxx.xxx 未訂閱
SimpleRoundTo with Double
在 XE7 中 Bug 還在

好像是 Trunc 組合語言出錯

不知道 XE8 修正了沒?
aftcast
站務副站長


發表:81
回覆:1485
積分:1763
註冊:2002-11-21

發送簡訊給我
#12 引用回覆 回覆 發表時間:2015-05-19 16:06:42 IP:114.32.xxx.xxx 訂閱
這種改了也不會增加收入的bug,我看到 xe20可能也不會好。而且許多的delphier都自幹這個問題了…就參考前人做法吧。喔,對了,我是沒裝 xe8 ,但我不相信解決了。這種陳年的bug,在c++ builder更是多,report  n 次也沒得到回應 (是啦,delphi都沒處理了,cb要叫什麼? )。
pd大 有用 xe8,看看他有沒有時間幫你測… > <
;-)
===================引 用 GrandRURU 文 章===================
SimpleRoundTo with Double
在 XE7 中 Bug 還在

好像是 Trunc 組合語言出錯

不知道 XE8 修正了沒?
------


蕭沖
--All ideas are worthless unless implemented--

C++ Builder Delphi Taiwan G+ 社群
http://bit.ly/cbtaiwan
GrandRURU
站務副站長


發表:240
回覆:1680
積分:1874
註冊:2005-06-21

發送簡訊給我
#13 引用回覆 回覆 發表時間:2015-05-20 10:21:08 IP:59.120.xxx.xxx 未訂閱
目前已知 Double 型別會有精度問題
但如果一開始就宣告 Extended,精度問題就自然而然地排除了

如果 Double 想排除所謂精度的 "Bug"
可以按 wiseinfo 大的寫法
[code Delphi]
function RoundEx(vValue: Double; vScale: Integer): Extended;
var
EValue: Extended;
begin
EValue := StrToFloat(FloatToStr(vValue)); //这是必须的;FormatFloat也同样必须这样才正确
Result := SimpleRoundTo(EValue, vScale);
end;
[/code]
如果有需要額外做 Extended
可以再使用 overload 重載函式

以上
Main Chen
高階會員


發表:29
回覆:135
積分:127
註冊:2002-10-07

發送簡訊給我
#14 引用回覆 回覆 發表時間:2015-05-20 12:33:31 IP:220.134.xxx.xxx 訂閱
用 XE7 及 XE8 測試 SimpleRoundTo(61.3035, -3); 結果都是 61.304
it1506
初階會員


發表:32
回覆:89
積分:49
註冊:2011-02-16

發送簡訊給我
#15 引用回覆 回覆 發表時間:2015-05-22 08:24:12 IP:59.120.xxx.xxx 未訂閱
用currency 這型別再去formatfloat 好像就沒這問題

金額型別(Currency)
範圍:-922337203685477.5808~922337203685477.5807
Currency型別變數是以64個位元(8 Bytes)、整數的方式來儲存資料
以2的補數方式來表示正負
除以10000表示小數
P.D.
版主


發表:603
回覆:4038
積分:3874
註冊:2006-10-31

發送簡訊給我
#16 引用回覆 回覆 發表時間:2015-05-25 10:52:55 IP:118.160.xxx.xxx 未訂閱
我自已由久以來的做法, 如果要得到精準的進位, 自己寫進位公式,
通常我就是把原值 * 10^n 放大(看你要取決到小數幾位), 然後 5, 再回除10^n, 到目前為止並沒有發生進位的問題
例如 10.449 希望得到答案是 10.45
10.449 * 10^3 = 10449 5 = 10454 / 10 ^ 3 = 10.454, 改成 string "10.454", 取回 "10.45" 再換回數值 = 10.45
===================引 用 aftcast 文 章===================
這種改了也不會增加收入的bug,我看到 xe20可能也不會好。而且許多的delphier都自幹這個問題了…就參考前人做法吧。喔,對了,我是沒裝 xe8 ,但我不相信解決了。這種陳年的bug,在c builder更是多,report n 次也沒得到回應 (是啦,delphi都沒處理了,cb要叫什麼? )。
pd大 有用 xe8,看看他有沒有時間幫你測… > <
;-)


不知道 XE8 修正了沒?
編輯記錄
P.D. 重新編輯於 2015-05-25 10:53:57, 註解 無‧
P.D. 重新編輯於 2015-05-25 10:57:47, 註解 無‧
it1506
初階會員


發表:32
回覆:89
積分:49
註冊:2011-02-16

發送簡訊給我
#17 引用回覆 回覆 發表時間:2015-09-15 08:17:46 IP:59.120.xxx.xxx 未訂閱
真的蠻討厭 double 跟float的
大多都是用 currency 跟 extend
系統時間:2024-05-08 21:51:19
聯絡我們 | Delphi K.Top討論版
本站聲明
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。
2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。
3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇!