PALMisLIFE 討論區

標題: [問題]javascript 求餘數 [列印本頁]

作者: 小賤健    時間: 2008-5-28 11:26
標題: [問題]javascript 求餘數
平常工作多半是寫 C#/VB.Net 為主,幾乎不太寫 javascript 的,有也多半只是用來控制畫面的顯示項而已XD
就在剛剛,遇到一個很奇妙的情況


  1. var iAmount = 920;
  2. var a = iAmount % 0.92;
  3. var b = iAmount * 100 % 92;
  4. var c = parseInt(iAmount / 0.92);
  5. var d = (a==0)?(c):(c+1);
複製代碼


為什麼 b==0 而 a!=0
原本是要由 a 求得餘數,再來計算 d 值。但程式結果一直不正確,tracking 後竟然發現 a = 0.9199999999999601,然後才異想天開,改成 b 的算式,噗~結果就正確了,因為 b 確實是為 0 的XD

我想問,這是什麼原因呢。謝謝各位前輩先

PS: runs on Windows Server 2003,IE7/FF2

[ 本文最後由 小賤健 於 2008-5-28 13:11 編輯 ]
作者: closer    時間: 2008-5-28 13:30
標題: Re: javascript 求餘數
Update: Javascript 應該是用「倍精準」的浮點數而非「單精準」,因此稍微修改了一下數字)

我倒覺得很奇怪:因為我從來沒想過用浮點數去取餘數。
(因為我最熟的 C 是不能這麼做的)

然後,你這個問題是發生在電腦裡的二進位浮點數表示法。
依現在一般通用的浮點數表示法(IEEE標準),0.92 會是一個「循環小數」,而非精確的一個數。
化成 10 進位來看的話,會是 0.92000000000000004。
所以 920 % 0.92000000000000004 是除不盡的。

所以,如果你做個實驗,
var b = iAmount*100 % (0.92 * 100),b 也不會是 0。

至於為什麼 0.92 會變成 0.92000000000000004.....
這故事很長,得用另一篇文章去講。
你真的想聽嗎? :p

可以參考下面兩個 URL:
http://www.ods.com.ua/win/eng/web-tech/js/htm/07-01.phtml
http://zh.wikipedia.org/wiki/IEEE_754

[ 本文最後由 closer 於 2008-5-28 14:19 編輯 ]
作者: 小賤健    時間: 2008-5-28 18:17
標題: Re: [問題]javascript 求餘數
至於為什麼 0.92 會變成 0.92000000000000004.....
這故事很長...

願聞其詳
C 我已經忘光光了。
而 C++ / C#我確是沒有這層限制了
作者: zombie    時間: 2008-5-28 18:25
標題: Re: Re: [問題]javascript 求餘數
原文由 小賤健 於 2008-5-28 18:17 發表

願聞其詳
C 我已經忘光光了。
而 C++ / C#我確是沒有這層限制了


C++應該還是有這個限制的,主要在於記憶體中的表示方式。
C#的話,除非是用decimal,不然,應該還是會由於相同的原因,發生類似的問題。
例如最常見的例子就是:
float delta = 0.1d;
float sum = 0.0d;
for(int i=0; i < 10; i ++){
   sum += delta;
}
if(sum == 1.0d)
   printf("equal";
else
  printf("not equal";

[ 本文最後由 zombie 於 2008-5-28 18:29 編輯 ]
作者: closer    時間: 2008-5-28 22:39
標題: Re: Re: [問題]javascript 求餘數
原文由 小賤健 於 2008-5-28 18:17 發表

願聞其詳
C 我已經忘光光了。
而 C++ / C#我確是沒有這層限制了


這是浮點數表示法的限制,不是語言的限制。
除非你用的語言非常高階,會幫你做很多事情、用自己的方法表示小數(例如 VB 和 C# 的 "decimal" 型別),
否則在現代的 CPU 上處理浮點數 (floating-point),都會遇上這樣的問題。

原本是想詳細解說一下 IEEE 754 的內容,但真的要詳細討論的話會太複雜。
尤其是 IEEE 754 用了一些方便電腦實作的技巧,要用人腦理解就更麻煩一點。
所以就簡單講一下概念好了。

我們從十進位的浮點數開始看:

12.34

這個很簡單,小學畢業就知道它是什麼意思。
我們如果用科學一點的方法來表示它,它就是:

1 * ( 10 ^ 1) + 2 * (10 ^ 0) + 3 * (10 ^ (-1)) + 4 * (10 ^ (-2))

"x ^ y" 表示 "x 的 y 次方"

10 的 -1 次方就是 1/10,也就是 0.1。這個是高中數學。 :p

好,那麼,二進位的浮點數其實也是類似的觀念。所以下面這個數字:

    1010.1101b

化成十進位來看的話,就是

1 * (2^3) + 0 * (2^2) + 1 * (2^1) + 0 * (2^0) + 1 * (2^(-1)) + 1 * (2^(-2) + 0 * (2^(-3)) + 1 * (2^(-4))
= 8 + 2 + 1/2 + 1/4 + 1/16
= 10.8125

正因為二進位的小數部份是由「2 的 n 次方之一」組成的,所以並不是所有的「十進位有限小數」都能精確地轉成「二進位有限小數」。
例如 0.2 就只能變成:

0.001100110011........b

上面的這個例子轉成十進位會變成 0.199951171875。
如果再取更多位數的話會更精確。

所以結論是:浮點數是個很不精確的東西。 XDDDDD

像是銀行的帳就絕對不能用浮點數處理!
這也是為什麼 VB 和 C# 會有 "decimal" 這樣的型別。

有問題再討論.....如果你還有興趣的話。
作者: 小賤健    時間: 2008-5-28 22:59
標題: Re: [問題]javascript 求餘數
有問題再討論...

經 closer 兄清晰的解說,我有明白了

然後也有去翻了一下 MSDN(http://msdn.microsoft.com/en-us/library/se0w9esz(VS.80).aspx),它也有相關的說明。
平時不太有機會碰到這方面的數值分析,再加上自己寫 code 的習慣(C#/VB),也都是採用比較嚴格的強型別定義,所以上述的問題也都沒碰到過。要不是今天得用 javascript 來處理,我大概這輩子都不會記得有這樣的限制吶@_@

以上。多謝 closer、zombie 二位的說明,多謝多謝啊

---
而至於 closer 提及的銀行帳務的數值處理,就之前處理過某大銀行的經驗,是採用大數陣列的方式來運算數字的。

[ 本文最後由 小賤健 於 2008-5-28 23:17 編輯 ]
作者: zombie    時間: 2008-5-28 23:56
標題: Re: Re: [問題]javascript 求餘數
原文由 小賤健 於 2008-5-28 22:59 發表

經 closer 兄清晰的解說,我有明白了

然後也有去翻了一下 MSDN(http://msdn.microsoft.com/en-us/library/se0w9esz(VS.80).aspx),它也有相關的說明。
平時不太有機會碰到這方面的數值分析,再加上 ...


補充一下,在資料庫,也會有因為相同原因所造成的問題,所以,才會有類似oracle的decimal型別的出現,
這個是設計資料庫的時候需要注意的地方,不然,每個月光為了追那個一元五角的,就追到瘋掉了。




歡迎光臨 PALMisLIFE 討論區 (http://f.pil.tw/) Powered by Discuz! X2.5