以印尼银行(Bank Indonesia - 印尼的中央銀行)的印尼盾匯率為例:印行會用當日雅加達時間上午8.00-9.45銀行間同業交易的USD/IDR即期匯率,計算加權平均,然後在上午10.00公佈該這個參考價-雅加達銀行同業即期匯率(JISDOR),和現時22種貨幣的參考匯率,發佈的數字只有買入價、賣出價,並沒有中間價。
要知道一般大眾看的就是中間價。談到日元歐元下跌又是旅行良機時,你不會分別說買入賣出價是多少。某數據供應商亦合理地有提供中間價 ( =(買入+賣出)/2 )。只是,數據商預設準確至2個小數位(2 d.p.) ,當我們留意這個數據的準確性時,卻偶然出現細小的誤差,例如:
中國 CNY: (2075.81+2055.24)/2 =2065.525
馬來西亞 MYR:(3177.46+3213.83)/2 =3195.645
但報出來的中間價分別是MYR:3195.65、CNY:2065.52,湊整時對0.005的處理一個是向下捨去、一個是向上進位,這點並不一致⋯⋯
然後(多次嘗試、詢問、查找後),終於了解到這是小數和浮點數間的誤差所以做成,用這個IEEE 754 Converter:
Ask:2075.81 會儲存為 $1.0135791301727295 \times 2^{11}$,大約是2075.8100585..
Bid:2055.24 會儲存為 $1.0035351514816284 \times 2^{11}$,大約是2055.2399902..
計算中間價時,Mid:2065.525 會儲存為 $1.0085570812225342 \times 2^{11}$,大約是2065.5249023...當提供預定的2個小數位時,就會湊整為2065.52而非2065.53。
這樣的誤差可以有多大呢?會不會當數據商從印尼銀行得到2個小數位的Bid/Ask儲存成Float後,我們直接拿取2個小數位的Bid/Ask也會出現這個錯誤呢?這樣便要再了解一下背後的運作了。
我們開始時提到電腦如何儲存整數,也有一種Float的格式儲存數字。而32Bit Float為例,先有 1 Bit(Sign)用作表示正負數,之後 8 Bit (Exponent)用作表示2的次方[加上127來避免負號],最後 23 Bit (Mantissa)用作表示1.xxxxxx。
0 | 10000101 | 10010 00000 00000 00000 000 |
Sign (1 Bit) | Exponent (8 Bit) | Mantissa (23 Bit) |
100 - | 0 | 10000101 | 10010 00000 00000 00000 000 |
(Sign:+:$0_{2}$ )
(Exponent:$2^{6}$: $2^{(127+6)} = 2^{133}=10000101_{2}$)
(Mantissa:1.05625: $0.5625=1 + 2^{-1} + 2^{-4} = 10010 00000 00000 00000 000_{2}$)
但有些數字用23個位的Mantissa其實不夠記錄,例如0.1用2進制表示可以寫成 ${+1.1001100110011...(0011)... \times 10^{-10}}_2$ (用10進制表示: $+1.600000023841858...\times2^{-4}$),當中0011會不斷重複,十進制下的0.1轉換成二進制就是循環小數$0.0001011\overline{0011}_2$。 但電腦內只會紀錄23個位、第24個位開始被砍掉的部份就會做成微少的誤差。
0.1 - | 0 | 01111011 | 10110 01100 11001 10011 001 |(...之後的被砍掉了)
0 | 01111011 | 10110 01100 11001 10011 001 | |
Sign (1 Bit) | Exponent (8 Bit) | Mantissa (23 Bit) | Error (0 Bit) |
回到印尼盾兌人民幣的中間價:
2065.525 - 0 | 1001010 | 00000 01000 11000 01100 110 |
用$float()$表示電腦中將Deciaml轉到為Float並儲存成32Bit的函式,以下運算可見誤差應該少於約0.00012。的確我們可以看到 2065.525 - 2065.5249023 = 0.00009... < 0.00012 的。
$$Error = |2065.525-float(2065.525)| \le 2^{-24} \times 2^{\lfloor log_22065.525\rfloor} = 2^{-13} \approx 0.00012$$
對於一般的數字x, 因為上式會包含x的Exponent, 我們可以改為考慮相對誤差:
$$Rel.Error = \frac{|x-float(x)|}{x} \le \frac{2^{-24} \times 2^{exponent}}{1 \times 2^{exponent}} =2^{-24} \approx 5.96 \times 10^{8} $$
這樣可以看到一個簡單的結果:相對誤差少於$2^{-24}$,大約 0.00000596%。
當給定一個2個小數位的數字 x,float(x)的誤差應該$\ge$0.005才可以被四捨五入至另一個數,$x \times 0.00000596 \% \ge 0.005$,也就是說:最少要大於83886.08的數字才有機會「從印尼銀行得到2個小數位的Bid/Ask儲存成Float後,我們直接拿取2個小數位的Bid/Ask也會出現這個錯誤」。
現在匯率數值最大的科威特大約是 1第納爾兌45,000盾,以上界線只是一個保守的Boundary,應該暫時不會出現0.01的誤差吧。
Useful link:
IEEE 754 Convertor
http://www.h-schmidt.net/FloatConverter/IEEE754.html
What Every Computer Scientist Should Know About Floating-Point Arithmetic
https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
How to correct rounding errors in floating-point arithmetic
https://support.microsoft.com/en-us/kb/214118
沒有留言:
張貼留言