定點數運算
在電腦中,定点数(英語:fixed-point number)是指用固定整數位數表達分數的格式,屬於实数数据类型中一種。例如美元常會表示到二位小數,以分來表示,即為一種定点数。有時定点数也會要求要有固定的整數位數。定点数与更复杂的浮点数相对。 在定点数表示法中,小數部份和整數部份一樣,也會表示為進制底数b的幂次,不過是以負數幂次來表示。最常見的定点数表示法是十进制(底数為10)和二进制(底数為2)。若儲存了n位的小數,其數值一定是b−n的整數倍。定点数表示法也會用來省略整數中較低位數的值,例如將金錢表示為1000美元的整數倍。 在人們處理有小數的十进制數字時,會在整數和小數之間加上小數點('.'或是',')。不過定点数中,整數和小數的位數長度是依程式的規畫來決定。 在机械计算器中主要會使用定點數運算。由於大多數現代的中央处理器就有浮点运算器(FPU),只有在特殊的應用中才使用定點數運算,例如低價的嵌入式系统微处理器以及单片机,這類的應用強調高需求速度,低電力需求及小集成电路區域,例如影像、視頻或数字信号处理,或是一些這種表示法比較適合問題本質的議題。後者的例子是会计学的金錢單位,非整數的金額也需要進行四捨五入,另一種情形是在產生函数的查找表。 表示法
定点数类型的值其实就是个整数,需要额外做比例进位,进多少位需要根据具体的定点数类型决定。例如 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。
精確值在二進制的定點數中,考慮二進制下的分數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以外的數字時,才能表示其精確值。 Q格式表示字长和二进制定点数中小数点位置的方法有很多种。下面的例子中,使用 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是總位元數。
軟體應用例
相關條目参考文献
|
Portal di Ensiklopedia Dunia