Loading AI tools
来自维基百科,自由的百科全书
在電腦中,定點數(英語:fixed-point number)是指用固定整數碼數表達分數的格式,屬於實數資料類型中一種。例如美元常會表示到二位小數,以分來表示,即為一種定點數。有時定點數也會要求要有固定的整數碼數。定點數與更複雜的浮點數相對。
![]() | 此條目可能包含原創研究。 (2021年8月23日) |
在定點數表示法中,小數部份和整數部份一樣,也會表示為進制底數b的冪次,不過是以負數冪次來表示。最常見的定點數表示法是十進制(底數為10)和二進制(底數為2)。若儲存了n位的小數,其數值一定是b−n的整數倍。定點數表示法也會用來省略整數中較低位數的值,例如將金錢表示為1000美元的整數倍。
在人們處理有小數的十進制數字時,會在整數和小數之間加上小數點('.'或是',')。不過定點數中,整數和小數的位數長度是依程式的規劃來決定。
在機械計數機中主要會使用定點數運算。由於大多數現代的中央處理器就有浮點運算器(FPU),只有在特殊的應用中才使用定點數運算,例如低價的嵌入式系統微處理器以及單晶片,這類的應用強調高需求速度,低電力需求及小集成電路區域,例如影像、影片或數碼訊號處理,或是一些這種表示法比較適合問題本質的議題。後者的例子是會計學的金錢單位,非整數的金額也需要進行四捨五入,另一種情形是在產生函數的尋找表。
小數數值 | 用整數表示的值 |
---|---|
0.00 | 0 |
0.5 | 50 |
0.99 | 99 |
2 | 200 |
−14.1 | −1410 |
314.160 | 31416 |
定點數類型的值其實就是個整數,需要額外做比例進位,進多少位需要根據具體的定點數類型決定。例如 1.23 使用 1/1000 縮放係數的定點數表示時是 1230;1,230,000 使用 1000 縮放係數的定點數表示也是 1230。與浮點數不同,相同類型的定點數中所有值的縮放係數都是一致的,在計算過程中也保持不變。此表示法可以用標準的整數算術邏輯單元來進行有理數的計算。
二進制定點數下的負數常常會用二補碼型式下的有號整數表示,其中隱含着縮放係數(或小數長度)。數值的正負號會依最高位元而定(1表示負值,0表示正值)。就是小數碼數等於或大於整個數字的位置也是如此。例如,8位元二進制有號整數(11110101)2 = −11,若分別配合-3, +5及+12的隱含小數長度,數值為−11/2−3 = −88、−11/25 = −0.34375、和−11/212 = −0.002685546875。
另一種作法,負號可以用「符號-大小」的有符號數處理表示法以整數來表示,另外表示正負號,也有隱含的縮放係數。此作法常用於十進制的定點數貞算中。例如有號的十進制五位數 (−00025)10,配合-3, +5, and +12的隱含十進制小數長度,數值為−25/10−3 = −25000、−25/105 = −0.00025和−25/1012 = −0.000000000025。
程式一般會假設所有儲存在變數中的定點數,或是指令集架構產生的定點數,都會有相同的縮放係數。程式設計者可以依需要的測量精度以及實際數值的範圍來選擇縮放係數。
變數或是公式的縮放系數不一定會直接在程式碼中出現。好皊軟件工程實務會要求在軟件文件中說明縮放系數,至少也要在原始碼的註釋中說明。
定點數的最大值,可以通過將其內部所使用的整數的最大值乘以縮放係數求得,最小值同理。
為了效率考量,縮放係數(scaling factor)一般會是基數b(2 或是 10)的正冪次,或是負冪次,因此實際內部仍然可以用類似整數的方式處理。不過縮放係數也需依應用而定。因此許多的數字可能其數值其實是用二進制記錄,但為了使用方便人類讀寫,縮放係數仍選擇10的冪,10的冪的縮放係數也可以配合國際單位制,因為選擇特定的縮放系數,可能相當於使用另外一個大小較適合的單位,例如使用厘米或微米,而不是使用米。
不過有時也會使用其它縮放係數,例如可以用 1/3600 縮放係數的定點數來表示以小時為單位的時間值,可以精確到秒。
就算針對定點數進行最仔細的四捨五入,縮放係數S的定點數,其誤差最大會到其整數數±0.5,因此誤差的實際數值範圍是±0.5 S。若縮放系數越小,所得到結果越準確。
不過,縮放系數越小也表示相同長度下所能記錄的數值會比較小。定點數下可以儲存的最大數值會對應可儲存的最大整數,再乘以轉換系數,其最小值也類似。例如,下表列出在16位元有號二進制定點數下,不同小數碼元f下的縮放係數S,可以表示的最大值和最小值Vmin和Vmax,以及其精度δ = S/2。
f | S | δ | Vmin | Vmax |
---|---|---|---|---|
−3 | 1/2−3 = 8 | 4 | −0.262144 | +0.262143 |
0 | 1/20 = 1 | 0.5 | −0.032768 | +0.032767 |
5 | 1/25 = 1/32 | < 0.016 | −1024.00000 | +1023.96875 |
14 | 1/214 = 1/16384 | < 0.000031 | −2.00000000000000 | +1.99993896484375 |
15 | 1/215 = 1/32768 | < 0.000016 | −1.000000000000000 | +0.999969482421875 |
16 | 1/216 = 1/65536 | < 0.000008 | −0.5000000000000000 | + 0.4999847412109375 |
20 | 1/220 = 1/1048576 | < 0.0000005 | −0.03125000000000000000 | +0.03124904632568359375 |
在二進制的定點數中,考慮二進制下的分數a/2m(例如1/16或17/32),若其縮放系數為1/2n,且n ≥ m,即可以精確的用二進制定點數表示。不過大部份的十進制小數(如0.1或0.123)在二進制下會是無窮迴圈小數,因此無法用二進制表示其精確值。
十進制的情形也類似,考慮十進制下的分數a/10m(例如1/100或37/1000)若其縮放系數為1/10n,且n ≥ m,即可以精確的用十進制定點數表示。若分母是二的次冪的分數a/2m(如1/8 (0.125)或17/32 (0.53125),且n ≥ m,也可以精確的用十進制定點數表示。
若考慮有理數 a/b,其中a和b互質,b為正數。若用二進制的定點數表示,只有在b是2的次冪下才能表示其精確值,若用十進制的定點數表示,只有在b的質因數沒有2和5以外的數字時,才能表示其精確值。
表示字長和二進制定點數中小數點位置的方法有很多種。下面的例子中,使用 f 表示小數部分位數、m 表示整數部分位數、s 表示符號位位數、b 表示總位數,其中「位數」均為位元位。
定點數的運算比浮點數要快,需要的硬件也比較少。假如要計算的數值範圍事先就已知道,而且限制在較小的範圍內,定點數運算可以有效的使用其位元。例如,用32位元表示從0到1的數字,定點數下的誤差會小於1.2 × 10−10,而浮點數的誤差則為596 × 10−10,因為其中有九個位元表示指數以及指數的正負號,因此誤差較大。
使用定點數運算的程式,不會受到是否有浮點運算器(FPU)的影響,可移植性比浮點數要高。在IEEE 754普及之前,相同資料在浮點運算後的結果會依處理器廠商而不同,因此此一優點影響很大。
許多早期的嵌入型系統沒有浮點運算器,整數運算單元需要的邏輯門和集成電路較少,而且低速備下,用軟件模擬浮點運算的速度太慢,無法實用。早期個人電腦和電子遊戲機的處理器(例如Intel 80386和Intel 80486)都沒有浮點運算器。
定點格式下的絕對解像度(二個連續數值之間的差),在整個數值範圍下都是定值,例如是縮放係數S。相反的定點格式下的相對解像度,在整個數值範圍下都是定值,但其絕對解像度會隨數值而變化,變化甚至會到數個數量值。
在許多應用中,定點運算的四捨五入以及捨去誤差比較好分析。不過在定點運算下,程式設計者需要花較多的心力,例如為了避免溢位,需要確認計算中所有中間值的範圍,為了要調整到理想的縮放係數,需要有額外的程式處理訊號轉換。
十進制定點數常用在儲存貨幣價值上,因為浮點數的複雜捨入原則,往往會造成負擔。例如開源的現金管理應用程式,GnuCash,以C語言撰寫,就因為此原因從1.6版起由浮點數改為定點數。
自從1960年代末期,一直到1980年代,二進制定點數常用在要用到大量數學實時計算的程式,例如飛行模擬器以及核電廠控制演算法。在許多數碼訊號處理應用或是客製的微處理器中仍常使用定點數。像有關角度的計算,就會使用二進制角度測量(BAM)。
在STM32G4系列的CORDIC協同處理器,以及JPEG影像壓縮使用的離散餘弦變換(DCT)演算法,都使用二進制定點數。
若要針對二個縮放系數相同的數字相加或相減,直接針對數字運算即可,運算結果也會有相同的縮放系數,因此可以儲存在變數中,或是用在後續的運算中。只要運算過程沒有算術溢位(也就是運算過程和結果都可以正確的儲存),其結果就會是精確的。若二個縮放系數不相同的數字要相加減,需轉換到相同的縮放系數,才能相加減。
若要將二個定點數相乘,可以直接將其數字相乘,再假設最後積的縮放系數是被乘數和乘數縮放系數的乘積即可。只要沒有捨入,也沒有運算溢位,結果會是精確的。
例如,要將123(縮放係數1/1000,實際數值是0.123)和25(縮放係數1/10,實際數值是2.5)會得到123×25 = 3075,縮放係數是(1/1000)×(1/10) = 1/10000,因此實際數值是0.3075。另一個例子是將第一個數字和155(縮放係數1/32,實際數值是155/32 = 4.84375)相乘,所得的數字是123×155 = 19065,其縮放係數是(1/1000)×(1/32) = 1/32000,因此實際數值是19065/32000 = 0.59578125。
為了避免溢位,儲存乘積的變數長度會比被乘數和乘數的要長,例如被乘數和乘數分別是十進制的二位數,乘積需要用十進制的四位數來儲存,才不會溢位。
若要將二個定點數相除,可以直接將其數字相除,再假設最後商的縮放系數是被除數和除數縮放系數相除後的商即可。一般來說,相除時會出現捨入,因此結果無法完全精確。
例如,3456(縮放係數1/100,實際數值34.56)和1234(縮放係數1/1000,實際數值1.234)相除會得到3456÷1234 = 3(有捨入),其縮放係數為(1/100)/(1/1000) = 10,因此實際數值是30。若第一個數除以155(縮放係數1/32,實際數值155/32 = 4.84375),會得到3456÷155 = 22(有捨入),其縮放係數為(1/100)/(1/32) = 32/100 = 8/25,因此數值為22×32/100 = 7.04。
在結果不精確時,若被除數使用較小的縮放係數,可以縮小(甚至消除)因為捨入產生的誤差。例如,r = 1.23,表示為定點數123,縮放係數1/100,而s = 6.25,表示為6250,縮放係數1/1000,相除後的結果是123÷6250 = 0(有捨入),其縮放係數為(1/100)/(1/1000) = 10。若r先轉換為縮放係數1/1000000的數字,定點數表示會是1/1000000,其結果會是1,230,000÷625 = 197(有捨入),其縮放係數為1/1000(實際數值是0.197)。其精確值是1.23/6.25 = 0.1968,後者的計算結果比較接近。
在定點數運算中,常常需要調整定點數的縮放係數,以下是一些可能的原因:
若要將縮放系數R的定點數轉換為縮放系數S的定點數,整數需要乘以R/S。以1.23(= 123/100)為例,若要從縮放系數R=1/100,轉換為縮放系數S=1/1000,整數123要乘以(1/100)/(1/1000) = 10,因此即為1230/1000。
若二個縮放系數都是基數的次冪,調整縮放系數只是除去較低位數的數字,或是在較低位數增加零。不過,此一運算不能改變符號位元,因此需要像算術移位運算一樣的方式,延伸其符號位元。
若R不能被S整除(特別是新的縮放系數S比原來的縮放系數R要大),產生的整數需要進行數值修約。
若r和s是定點數,其縮放系數分別為R和S,運算r ← r×s需要將整數相乘,再除以S,所得結果可能會有數值修約,也有可能會溢位。
例如,二個定點數的縮放系數都是1/100,將1.23乘以0.25,其整數相乘是123乘以25,得到3075,而縮放系數是1/10000,為了還原到1/100的縮放系數,3075需乘以1/100,也就是除以100,依數值修約方法的不同,所得結果可能是31(0.31)或是30(0.30)。
而運算r ← r/s需要將整數相除,再乘以S,所得結果可能會有數值修約,也有可能會溢位。
若要將浮點數轉換為定點數,可以將浮點數除以縮放系數S,再修約到最接近的整數。需確認對應的變數有足夠的大小儲存所得的結果。依縮放系數、變數長度,以及數值大小的不同,轉換有可能需要修約。
若要將定點數轉換為浮點數,可以將定點數乘以縮放系數S。若整數的絕對值超過224(IEEE單精度浮點數)或253(IEEE雙精度浮點數),可能會需要修約。若|S|非常大或是非常小,可能會有溢位或算術下溢(小於可以表示的最小數)的情形。
一般的處理器沒有特別支援定點數的運算,不過大部份二進位元運算的處理器會有快速的位元運算指令,因此可以在很短的時間進行和二的乘冪的乘法或除法,有些還有算術移位指令,在和二的乘冪相乘除的同時,還可以保留數字的正負號。
早期像是IBM 1620或是Burroughs B3500的電腦會使用二進碼十進數(BCD)表示法來處理整數,在十進制下每一個位數會分別用四個次元來儲存,有些處理器還使用此一運算方式,因此可以用移位的方式來處理和十的乘冪相乘除的運算。
有些DSP架構會支援特定的定點運算格式,例如有號的n位元數字,其中有n−1位是儲存小數(其儲存數值範圍從-1到幾乎是+1的),其中會有特別包括正規化的乘法指令,在相乘後將小數碼數從2n−2位調整回n−1位。若DSP沒有支援此一機能,程式設計者需要用夠大的暫存器或是暫存變數來儲存此數值,再另外用程式進行正規化處理。
若運算結果的數值太大,超過要儲存位置可以儲存的範圍,就會出現溢位。若是加法或是減法,所得結果會比運算數字多一個位元,若是乘法,所得結果的位元數會是二個運算數字位元數的和。
若出現溢位,最高位元的資訊可能會因此而更改,若儲存空間有n位元,所儲存的會是除以2n的餘數,而最高位元會視為符號位元,因此結果的大小及正負號都可能改變。
有些處理器有溢位旗標,或是在溢位時進行的例外處理。有些處理器則會有飽和運算,若相加或是相減的結果可能會溢位,考慮其正負號,儲存相同符號,絕對值最大的數值。,
飽和運算只保留了正負號,無法保留結果的大小,在實務上可能不太實用。一般比較簡單及安全的作法是選擇適當的縮放係數以及變數儲存長度,避免溢位的可能性,或者在運算之前先檢查運算結果是否可能溢位。
有些程式語言明確支援定點數表示法及運算,著名的有PL/I、COBOL、Ada、JOVIAL及Coral 66。這些程式語言中提供定點數的數值資料類型,可能是二進制或十進制,可以指定給變數或是函數。編譯器會在處理有關的程式碼時自動進行轉換,例如處理類似r ← s × t + u的程式碼,讀寫這些變數的值,或是將這些資料類型轉換為浮點數時。
這類的程式語言多半是在1940年至1990年之間設計的,現今的程式語言多半都不提供定點數資料類型,也不提供相關的系數轉換。有些較古老,但仍很受歡迎的程式語言也是如此,例如Fortran、C語言和C++。浮點處理器的普及,以及嚴格標準化的行為,大幅的減少二進制定點數的需求。有些程式語言支援十進制浮點數(例如C♯和Python),也去除了大多數需要十進制固點數的需求。很少數需要固點數的情形,會由程式設計師寫程式實現,並且有明確的系數轉換,這在任何程式語言都可以實現。
另一方面,所有關聯式資料庫以及SQL表示法都支援用十進制定點數來進行數值的儲存以及運算。PostgreSQL有一種特別的numeric型態,可以精確的儲存數值,位數最多可以到1000位[5]。
ISO在2008年針對C語言提出了有關定點數資料型態的擴充提案,目的為了方便程式在嵌入式處理器上執行[6]。GCC的編譯器也支援定點數[7][8]。
以下是二個三位小數的定點數乘法。
(10.500)(1.050) =1*10.500 + 0.050*10.500 = 10.500+0.525000=11.025000
因為有三位小數,因此保留小數最後的0。為了要重整為整數乘法,先將數字都乘以1000,使數字都變成整數,之後再乘以二次,使最終結果不會改變,其算式如下
(10.500)(10^(3)) (1.050)(10^(3)) (10^(-3))(10^(-3)) = (10500)(1050) (10^-6) = 11 025 000 (10^-6) = 11.025000
若用其他的進制(例如方便計算的二進制),也可以用類似的原理來進行,因為二進制的左移或右移都相當是數值的乘2或是除2。十進制的三位數接近二進制的十位數,因此可以將0.5表示為有十位小數的二進制數字。最低的近似值是0.0000110011.
10= 8+2=2^3+2^1 1=2^0 0.5= 2^-1 0.05= 0.0000110011(二進制)
乘法會變成
(1010.100)(2^3)(1.0000110011)(2^10) (2^-13) =(1010100)(10000110011) (2^-13) =(10110000010111100) (2^-13) =1011.0000010111100
若轉為十進制,保留三位小數,會是11.023。
考慮計算1.2乘5.6的乘積,使用有16位元小數的二進制定點數。為了要表示這二個數字,需先將二個數字乘以216,所得的是78643.2和367001.6,再四捨五入到最近整數,是78643和367002,此數字可以放在32位元的word中,用二補碼的有號數表示法。
二個數字相乘會得到35位元的整數28862138286,小數碼數則有32位元。若放在32位元的整數中,會出現溢位,而且會失去最高位的位元,因此,應該存在有號的64位元整數變數或是暫存器中。
若結果要儲存在也是16位元小數的資料格式中,上述的整數需要除以216,結果會是440401.28,再四捨五入到最近整數,可以用先加上215,再將數字右移16位元的方式達到相同效果,其結果是440401,表示的數值是6.7199859619140625。考慮此格式的精度,結果可以表示為6.719986 ± 0.000008(不考慮運算近似產生的誤差)。正確結果是1.2 × 5.6 = 6.72。
為了要精確的說明定點數的參數,有許多不同的標示方式。在以下的表中,f表示小數碼元數,m表示整數碼元數,s表示符號位元,而b是總位元數。
PIC
命令功能(directive)來標示。例如PIC S9999V99
代表六位數整數,二位小數的的十進制有號數[9]。REAL FIXED BINARY (
p,
f)
的construct來標示,標示二進制定點數,其中共有p位數(不考慮符號),其中小數是f位數,總共的有號整數有p+1位數,其轉換系數是1/2f。也可以用COMPLEX
代替REAL
,用DECIMAL
代替BINARY
來表示十進制。type F is delta 0.01 range -100.0 .. 100.0
,表示用二補碼格式的有號二進制表示法,其中小數碼數是7位元(縮放係數是1/128),至少要用15位元來儲存(確保實際範圍可以從-128.00到將近+128.00)[10]。Q
f來表示有f位小數的有號定點數,例如Q15
是縮放係數為1/215的二補碼有號數。而 Q
m.
f額外的標示數字的整數部份有m位(不算符號位元)。因此Q1.30
表示有一位整數,30位小數的二進制定點數,可以存在32位元二補碼整數中,縮放係數是1/230[1][11]。ARM架構也有類似的標示法,但其中的m有包括符號位元,因此上述的格式要改為Q2.30
[12][13]。fx
m.
b來表示共有b位元,小數有m位元的二進制定點數,因此是b位元的整數,縮放係數是1/2b−m。因此fx1.16
是十六位元數字,整數部份1位元,小數部份15位元[3]。:
m:
f的標示方式,其中s表示是否有符號位元[14]。例如,0:5:3
表示8位元整數,縮放係數是1/23的無號數。<
s,
b,
m>
,表示FXP定點數的格式。s可以是'+'或'±',表示無號數或是二補碼的有號數。b是總位元個數,而m是整數部份的位數。