您好,登錄后才能下訂單哦!
使用十進(jìn)制浮點(diǎn)數(shù),可以避免二進(jìn)制浮點(diǎn)數(shù)與我們習(xí)慣的十進(jìn)制數(shù)之間的表示誤差.這個(gè)在金融領(lǐng)域是非常重要的.但是計(jì)算機(jī)基本都只能對(duì)二進(jìn)制浮點(diǎn)數(shù)進(jìn)行計(jì)算,也就是IEEE754格式表示的浮點(diǎn)數(shù).很多程序都會(huì)自己模擬十進(jìn)制浮點(diǎn)數(shù)的計(jì)算.為了統(tǒng)一,IEEE754做了擴(kuò)展,包括了十進(jìn)制的浮點(diǎn)數(shù).
IEEE 754-2008里面規(guī)定了十進(jìn)制浮點(diǎn)數(shù)的一些規(guī)范.不過里面沒有說具體的二進(jìn)制表示方法.只是規(guī)定了32位,64位,128位的十進(jìn)制浮點(diǎn)數(shù)的表示范圍和有效位數(shù). 因?yàn)榫唧w一個(gè)浮點(diǎn)數(shù)的二進(jìn)制里面每個(gè)位表示啥,都是每個(gè)機(jī)器自己決定的.不需要跟外界一致.只是在傳輸?shù)臅r(shí)候要保證數(shù)據(jù)的精度和范圍一致就行了.下表來自wikipedia,列出了每種浮點(diǎn)數(shù)的有效位數(shù),指數(shù)的范圍.
Name | Common name | Base | Digits | E min | E max |
binary16 | Half precision | 2 | 10+1 | -14 | 15 |
binary32 | Single precision | 2 | 23+1 | -126 | 127 |
binary64 | Double precision | 2 | 52+1 | -1022 | 1023 |
binary128 | Quadruple precision | 2 | 112+1 | -16382 | 16383 |
decimal32 |
|
10 | 7 | -95 | 96 |
decimal64 |
|
10 | 16 | -383 | 384 |
decimal128 |
|
10 | 34 | -6143 |
6144 |
實(shí)際的系統(tǒng)中,十進(jìn)制浮點(diǎn)數(shù)有兩種表示方法,分別是Densely Packed Decimal(密集十進(jìn)制數(shù))和Binary Integer Decimal(二進(jìn)制整數(shù)表示的十進(jìn)制數(shù)).
DPD表示方便轉(zhuǎn)換成十進(jìn)制的浮點(diǎn)數(shù)字符串,但是需要專門的計(jì)算單元來做計(jì)算,軟件模擬比較麻煩.
而BID表示更直觀,轉(zhuǎn)換到二進(jìn)制會(huì)比較容易.很方便用二進(jìn)制的整數(shù)運(yùn)算單元來計(jì)算.
所以Power6上有了硬件的十進(jìn)制浮點(diǎn)計(jì)算單元,就用DPD表示.而在x86 x64 cpu上沒有十進(jìn)制計(jì)算單元, 各種軟件實(shí)現(xiàn)的十進(jìn)制浮點(diǎn)庫(kù)默認(rèn)大都用BID方式表示.比如Intel就實(shí)現(xiàn)了一個(gè)開源的c 語(yǔ)言的十進(jìn)制浮點(diǎn)數(shù)庫(kù)。http://software.intel.com/en-us/articles/intel-decimal-floating-point-math-library/
十進(jìn)制浮點(diǎn)的意義,在于更符合人們的習(xí)慣,比如下面的例子
#include
<
stdio
.
h
>
int
main
(
)
{
double a
=
7
.
;
double b
=
0
.
00007
;
printf
(
"%d/n"
,
a
=
=
b
*
100000
)
;
}
正確的輸出應(yīng)該是1,但是實(shí)際的輸出結(jié)果是0,在做相等比較的時(shí)候,還不得不考慮一下這個(gè)誤差了。而某些時(shí)候誤差會(huì)在計(jì)算過程中累計(jì),變成比較明顯的錯(cuò)誤了。
如果用intel的十進(jìn)制浮點(diǎn)庫(kù)賴做這個(gè)計(jì)算,結(jié)果就會(huì)不同了。intel這個(gè)庫(kù)明顯還在試驗(yàn)階段,用起來比較麻煩。
int
main
(
)
{
Decimal64 a
,
b
,
c
;
_IDEC_round my_rnd_mode
=
_IDEC_dflround
;
_IDEC_flags my_fpsf
=
_IDEC_allflagsclear
;
a
=
bid64_from_int32
(
7
)
;
b
=
bid64_from_string
(
"0.00007"
,
my_rnd_mode
,
&
my_fpsf
)
;
c
=
bid64_mul
(
b
,
bid64_from_int32
(
100000
)
,
my_rnd_mode
,
&
my_fpsf
)
;
printf
(
"%d/n"
,
bid64_quiet_equal
(
a
,
c
,
&
my_fpsf
)
)
;
return 0
;
}
使用和double位數(shù)相同的Decimal64,結(jié)果就是1了。這里顯然不是精度的問題,而是十進(jìn)制浮點(diǎn)數(shù)能絲毫不變的表示十進(jìn)制的小數(shù)。
我們可以看到這里使用的是BID的表示方法。函數(shù)名前面都帶個(gè)bid前綴。
接下來,我們來具體看看BID的表示方法,我們可以把剛才程序中的a和c按照十六進(jìn)制輸出
printf("%llx/n%llx/n",a,c);
結(jié)果是
31c0000000000007
31200000000aae60
可見,兩個(gè)相等的十進(jìn)制浮點(diǎn)數(shù)的BID表示不一定是相同的。也就是說,一個(gè)數(shù)有多種表示方法。
a的表示里,最低位的16進(jìn)制數(shù)就是7,而c的表示里,最低的5位15進(jìn)制數(shù)aae60,其實(shí)就是十進(jìn)制的700000??磥磉@后面的就是有效數(shù)字部分了。查一下BID的表示方法,還是比較復(fù)雜的,有6種情況。最高位是符號(hào)位,這里當(dāng)然是0.符號(hào)位后面的兩位是00,01,或10時(shí),64位BID每個(gè)位的意義是這樣的,s后面的2位和之后的8位是指數(shù)部分,之后53位T和t都是有效數(shù)字部分
s 00eeeeeeee TTTtttttttttttttttttttt tttttttttttttttttttttttttttttt
s 01eeeeeeee TTTtttttttttttttttttttt tttttttttttttttttttttttttttttt
s 10eeeeeeee TTTtttttttttttttttttttt tttttttttttttttttttttttttttttt
而如果符號(hào)位后面的兩位是11,那么每一位的意義是
s 11 00eeeeeeee (100)Ttttttttttttttttttttt tttttttttttttttttttttttttttttt
s 11 01eeeeeeee (100)Ttttttttttttttttttttt tttttttttttttttttttttttttttttt
s 11 10eeeeeeee (100)Ttttttttttttttttttttt tttttttttttttttttttttttttttttt
這時(shí),有效數(shù)字前面就加了隱含的100.
這個(gè)BID表示的數(shù)的值就是 (-1)^S *T*10^(E-398) ,其中T 是實(shí)際的有效數(shù)字(就是說如果有隱含的100需要加上后計(jì)算),E是指數(shù),T,E都是2進(jìn)制表示的
還是回到我們的例子
a的二進(jìn)制數(shù)
0 0110001110 00000 00000000 00000000 00000000 00000000 00000000 00000111
指數(shù)部分就是0110001110,也就是398,所以a就是 7*10^(398-398) ,也就是7
而c的二進(jìn)制是
0 0110001001 00000 00000000 00000000 00000000 00001010 10101110 01100000
指數(shù)部分是 0110001001,也就是393, 所以c的值是 700000*10^(393-398), 還是7.
這就能看明白為啥同樣是7,二進(jìn)制表示卻不同。這也是十進(jìn)制浮點(diǎn)和二進(jìn)制浮點(diǎn)一個(gè)不同之處,十進(jìn)制浮點(diǎn)沒有規(guī)定一定要是哪一種表示。這也給相等比較帶來了一點(diǎn)麻煩。
power6 里面內(nèi)置了十進(jìn)制浮點(diǎn)計(jì)算單元,而power6上面的編譯器也就支持了內(nèi)置的十進(jìn)制浮點(diǎn)類型。前面已經(jīng)說了,power上面的十進(jìn)制浮點(diǎn)才用的是DPD表示方法。還是看個(gè)程序吧。下面這個(gè)程序在一個(gè)使用Power6的P520機(jī)器上,操作系統(tǒng)是AIX5.3 ML6, 用xlc 10.2編譯。_Decimal64就是64位的十進(jìn)制浮點(diǎn)。
int
main
(
int
argc
,
char
*
*
argv
)
{
long i
,
count
;
double dfund
,
dinterest
;
_Decimal64 Dfund
,
Dinterest
;
/
*
定義十進(jìn)制浮點(diǎn)類型的變量
*
/
long long value
;
union trans
{
_Decimal64 dv
;
int
av
[
2
]
;
}
transTemp
;
dfund
=
atof
(
argv
[
1
]
)
;
dinterest
=
atof
(
argv
[
2
]
)
;
Dfund
=
atodecimal
(
argv
[
1
]
)
;
Dinterest
=
atodecimal
(
argv
[
2
]
)
;
count
=
atoi
(
argv
[
3
]
)
;
/
*
下面把_Decimal64 類型的Dinterest轉(zhuǎn)換成兩個(gè)int,然后按照十六進(jìn)制格式顯示
*
/
transTemp
.
dv
=
Dinterest
;
printf
(
"value=%#x,%#x/n"
,
transTemp
.
av
[
]
,
transTemp
.
av
[
1
]
)
;
printf
(
"double fund=%20.10f interest=%40.30f/n"
,
dfund
,
dinterest
)
;
printf
(
"Decimal fund=%20.10Df interest=%40.30Df/n"
,
Dfund
,
Dinterest
)
;
/
*
printing them with the new printf specifiers
*
/
for
(
i
=
;
i
<
count
;
i
+
+
)
{
dfund
=
dfund
*
dinterest
;
Dfund
=
Dfund
*
Dinterest
;
/
*
performing maths
*
/
}
printf
(
"Print final funds/n"
)
;
printf
(
"double fund=%30.10f/n"
,
dfund
)
;
printf
(
"Decimal fund=%30.10Df/n"
,
Dfund
)
;
}
其中 atodecimal是自己寫的一個(gè)幫助函數(shù)
_Decimal64 atodecimal
(
char
*
s
)
{
_Decimal64 top
=
,
bot
=
,
result
;
int
negative
=
,
i
;
if
(
s
[
]
=
=
'
-
'
)
{
negative
=
1
;
s
+
+
;
}
if
(
s
[
]
=
=
'
+
'
)
s
+
+
;
for
(
;
isdigit
(
*
s
)
;
s
+
+
)
{
top
=
top
*
10
;
top
=
top
+
*
s
-
'
'
;
}
if
(
*
s
=
=
'
.
'
)
{
s
+
+
;
for
(
i
=
strlen
(
s
)
-
1
;
isdigit
(
s
[
i
]
)
;
i
-
-
)
{
bot
=
bot
/
10
;
bot
=
bot
+
(
_Decimal64
)
(
s
[
i
]
-
'
'
)
/
(
_Decimal64
)
10
;
}
}
result
=
top
+
bot
;
if
(
negative
)
result
=
-
result
;
return result
;
}
這個(gè)程序用xlc 10.2編譯時(shí),跟上參數(shù)表示使用硬件十進(jìn)制浮點(diǎn)。不過這樣會(huì)導(dǎo)致編譯出來的可執(zhí)行文件在power5以前的cpu上無法運(yùn)行。
運(yùn)行的時(shí)候輸入?yún)?shù) ./dfp_hw 1 1.00000091 6000000
dfp_hw是程序的名字,1就是程序里面的 fund,1.00000091是interest,也就是利息,6000000是count,輸出結(jié)果:
value=0x22180000,0x800001b
double fund= 1.0000000000 interest= 1.000000910000000020616539586630
Decimal fund= 1.0000000000 interest= 1.000000910000000000000000000000
Print final funds
double fund= 235.0968403429
Decimal fund= 235.0968403137
可以看到用double存儲(chǔ)利息,再輸出,就不再是1.00000091了,后面有一點(diǎn)誤差。而用_Decimal64存儲(chǔ)輸入結(jié)果,再輸出,是一點(diǎn)誤差都沒有。
然后把interest乘6000000次,也就是1.0000091的6000000次方,輸出的結(jié)果誤差就比較明顯了。用windows自帶的計(jì)算器可以驗(yàn)證,_Decimal64的結(jié)果是正確的。
現(xiàn)在來看看1.00000091的二進(jìn)制表示。也就是0x22180000,0x800001b,注意這里這個(gè)power機(jī)器是大端的,所以前面以前是高4字節(jié),后面是低4字節(jié)。連起來看,就是0x22180000 0800001b也就是
00100010 00011000 00000000 00000000 00001000 00000000 00000000 00011011
DPD表示方法也比較復(fù)雜,從高位開始看,第一位還是符號(hào)位0,DPD的規(guī)定如果符號(hào)位后面的兩位是00,01,或者10,那么每一位的意義如下
s 00 mmm (00)eeeeeeee (mmm)[tttttttttt][tttttttttt][tttttttttt][tttttttttt][tttttttttt]
s 01 mmm (01)eeeeeeee (mmm)[tttttttttt][tttttttttt][tttttttttt][tttttttttt][tttttttttt]
s 10 mmm (10)eeeeeeee (mmm)[tttttttttt][tttttttttt][tttttttttt][tttttttttt][tttttttttt]
其中,e是指數(shù),e的表示方法跟前面的BID方式很像。t和m是有效數(shù)字,其中,每10位t組成一個(gè)declet,表示一個(gè)3位的十進(jìn)制數(shù)。m實(shí)際的位置是在第4位到第6位,但是它邏輯上的位置是在那些t前面,所以用()表示放到e的后面。
因?yàn)?的10次方是1024,剛好能表示10的3次方。但是表示起來還是需要點(diǎn)技巧的,declet表示三位十進(jìn)制數(shù)的規(guī)則比較復(fù)雜,這也是這個(gè)表示方法叫Densely Packed Decimal(密集十進(jìn)制數(shù))的原因。下表是編碼的方式。b9-b0代表10個(gè)二進(jìn)制數(shù),d2 d1 d0代表3個(gè)十進(jìn)制數(shù)。
b9 |
b8 |
b7 |
b6 |
b5 |
b4 |
b3 |
b2 |
b1 |
b0 |
d2 |
d1 |
d0 |
編碼值 |
數(shù)位的模式 |
a |
b |
c |
d |
e |
f |
|
g |
h |
i |
0abc |
0def |
0ghi |
(0 – 7) (0 – 7) (0 – 7) |
3 位小數(shù)字 |
a |
b |
c |
d |
e |
f |
1 |
|
|
i |
0abc |
0def |
100i |
(0 – 7) (0 – 7) (8 – 9) |
兩位小數(shù)字,一位大數(shù)字 |
a |
b |
c |
d |
e |
f |
1 |
|
1 |
i |
0abc |
100f |
0dei |
(0 – 7) (8 – 9) (0 – 7) |
|
a |
b |
c |
d |
e |
f |
1 |
1 |
|
i |
100c |
0def |
0abi |
(8 – 9) (0 – 7) (0 – 7) |
|
a |
b |
c |
1 |
|
f |
1 |
1 |
1 |
i |
0abc |
100f |
100i |
(0 – 7) (8 – 9) (8 – 9) |
一位小數(shù)字,兩位大數(shù)字 |
a |
b |
c |
|
1 |
f |
1 |
1 |
1 |
i |
100c |
0abf |
100i |
(8 – 9) (0 – 7) (8 – 9) |
|
a |
b |
c |
|
|
f |
1 |
1 |
1 |
i |
100c |
100f |
0abi |
(8 – 9) (8 – 9) (0 – 7) |
|
x |
x |
c |
1 |
1 |
f |
1 |
1 |
1 |
i |
100c |
100f |
100i |
(8 – 9) (8 – 9) (8 – 9) |
三位大數(shù)字 |
就我們的例子來看一下,最低的10位是0000011011,看b3b2b1,這里是101,所以就是上表第3行的情況,三位數(shù)字就是 (0000)(1001)(0001)也就是091,然后看從低位數(shù)的第3個(gè)10位二進(jìn)制數(shù),也就是00100000000,這顯然是第一種情況,也就是100,連起來就是100000091,指數(shù)部分是390,那么這個(gè)十進(jìn)制的值就是 10^(390-398)*100000091 = 1.00000091.
通過這個(gè)簡(jiǎn)單的例子,就應(yīng)該對(duì)DPD方式的十進(jìn)制浮點(diǎn)表示方式有個(gè)大概的了解了。這個(gè)方式算起來比較麻煩,所以除非有硬件支持,軟件模擬的方式都不會(huì)使用的,但是DPD轉(zhuǎn)換成十進(jìn)制浮點(diǎn)的字符串表示就會(huì)很方便。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。