定點數運算

在電腦中，定點數是指用固定整數位數表達分數的格式，屬於實數資料型別中一種。例如美元常會表示到二位小數，以分來表示，即為一種定點數。有時定點數也會要求要有固定的整數位數。定點數與更復雜的浮點數相對。在定點數表示法中，小數部份和整數部份一樣，也會表示為進位制底數"b"的冪次，不過是以負數冪次來表示。最常見的定點數表示法是十進位制。若儲存了"n"位的小數，其數值一定是"b"−"n"的整數倍。定點數表示法也會用來省略整數中較低位數的值，例如將金錢表示為1000美元的整數倍。在人們處理有小數的十進位制數字時，會在整數和小數之間加上小數點。不過定點數中，整數和小數的位數長度是依程式的規畫來決定。在機械計算器中主要會使用定點數運算。由於大多數現代的中央處理器就有浮點運算器，只有在特殊的應用中才使用定點數運算，例如低價的嵌入式系統微處理器以及微控制器，這類的應用強調高需求速度，低電力需求及小積體電路區域，例如影像、或數字訊號處理，或是一些這種表示法比較適合問題本質的議題。後者的例子是會計學的金錢單位，非整數的金額也需要進行四捨五入，另一種情形是在產生函式的查詢表。表示法.定點數型別的值其實就是個整數，需要額外做比例進位，進多少位需要根據具體的定點數型別決定。例如 1.23 使用 1/1000 縮放係數的定點數表示時是 1230；1,230,000 使用 1000 縮放係數的定點數表示也是 1230。與浮點數不同，相同型別的定點數中所有值的縮放係數都是一致的，在計算過程中也保持不變。此表示法可以用標準的整數算術邏輯單元來進行有理數的計算。二進制定點數下的負數常常會用二補數型式下的有號整數表示，其中隱含著縮放係數。就是小數位數等於或大於整個數字的位置也是如此。例如，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。程式一般會假設所有儲存在變數中的定點數，或是指令集架構產生的定點數，都會有相同的縮放係數。程式設計者可以依需要的測量精度以及實際數值的範圍來選擇縮放係數。變數或是公式的縮放係數不一定會直接在程式碼中出現。好皊軟體工程實務會要求在軟體文件中說明縮放係數，至少也要在原始碼的註釋中說明。定點數的最大值，可以透過將其內部所使用的整數的最大值乘以縮放係數求得，最小值同理。縮放係數的選擇.為了效率考量，縮放係數的正冪次，或是負冪次，因此實際內部仍然可以用類似整數的方式處理。不過縮放係數也需依應用而定。因此許多的數字可能其數值其實是用二進位制記錄，但為了使用方便人類讀寫，縮放係數仍選擇10的冪，10的冪的縮放係數也可以配合國際單位制，因為選擇特定的縮放係數，可能相當於使用另外一個大小較適合的單位，例如使用釐米或微米，而不是使用米。不過有時也會使用其它縮放係數，例如可以用 1/3600 縮放係數的定點數來表示以小時為單位的時間值，可以精確到秒。就算針對定點數進行最仔細的四捨五入，縮放係數"S"的定點數，其誤差最大會到其整數數±0.5，因此誤差的實際數值範圍是±0.5 "S"。若縮放係數越小，所得到結果越準確。不過，縮放係數越小也表示相同長度下所能記錄的數值會比較小。定點數下可以儲存的最大數值會對應可儲存的最大整數，再乘以轉換係數，其最小值也類似。例如，下表列出在16位元有號二進制定點數下，不同小數位元"f"下的縮放係數"S"，可以表示的最大值和最小值"V"min和"V"max，以及其精度"δ" = "S"/2。精確值.在二進位制的定點數中，考慮二進位制下的分數"a"/2"m"在二進位制下會是無窮迴圈小數，因此無法用二進製表示其精確值。十進位制的情形也類似，考慮十進位制下的分數"a"/10"m"，且"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，因為其中有九個位元表示指數以及指數的正負號，因此誤差較大。使用定點數運算的程式，不會受到是否有浮點運算器的影響，可移植性比浮點數要高。在IEEE 754普及之前，相同資料在浮點運算後的結果會依處理器廠商而不同，因此此一優點影響很大。許多早期的嵌入型系統沒有浮點運算器，整數運算單元需要的邏輯閘和積體電路較少，而且低速備下，用軟體模擬浮點運算的速度太慢，無法實用。早期個人電腦和電子遊戲機的處理器都沒有浮點運算器。定點格式下的絕對解析度，在整個數值範圍下都是定值，例如是縮放係數"S"。相反的定點格式下的相對解析度，在整個數值範圍下都是定值，但其絕對解析度會隨數值而變化，變化甚至會到數個數量值。在許多應用中，定點運算的四捨五入以及捨去誤差比較好分析。不過在定點運算下，程式設計者需要花較多的心力，例如為了避免溢位，需要確認計算中所有中間值的範圍，為了要調整到理想的縮放係數，需要有額外的程式處理訊號轉換。應用.十進制定點數常用在儲存貨幣價值上，因為浮點數的複雜捨入原則，往往會造成負擔。例如開源的現金管理應用程式，GnuCash，以C語言撰寫，就因為此原因從1.6版起由浮點數改為定點數。自從1960年代末期，一直到1980年代，二進制定點數常用在要用到大量數學實時計算的程式，例如飛行模擬器以及核電廠控制演演算法。在許多數字訊號處理應用或是客製的微處理器中仍常使用定點數。像有關角度的計算，就會使用。在STM32G4系列的協同處理器，以及JPEG影像壓縮使用的離散餘弦變換演演算法，都使用二進制定點數。運算.加法和減法.若要針對二個縮放係數相同的數字相加或相減，直接針對數字運算即可，運算結果也會有相同的縮放係數，因此可以儲存在變數中，或是用在後續的運算中。只要運算過程沒有算術溢位，其結果就會是精確的。若二個縮放係數不相同的數字要相加減，需轉換到相同的縮放係數，才能相加減。乘法.若要將二個定點數相乘，可以直接將其數字相乘，再假設最後積的縮放係數是被乘數和乘數縮放係數的乘積即可。只要沒有捨入，也沒有運算溢位，結果會是精確的。例如，要將123相乘，所得的數字是123×155 = 19065，其縮放係數是(1/1000)×(1/32) = 1/32000，因此實際數值是19065/32000 = 0.59578125。為了避免溢位，儲存乘積的變數長度會比被乘數和乘數的要長，例如被乘數和乘數分別是十進位制的二位數，乘積需要用十進位制的四位數來儲存，才不會溢位。除法.若要將二個定點數相除，可以直接將其數字相除，再假設最後商的縮放係數是被除數和除數縮放係數相除後的商即可。一般來說，相除時會出現捨入，因此結果無法完全精確。例如，3456，其縮放係數為(1/100)/(1/32) = 32/100 = 8/25，因此數值為22×32/100 = 7.04。在結果不精確時，若被除數使用較小的縮放係數，可以縮小。其精確值是1.23/6.25 = 0.1968，後者的計算結果比較接近。縮放係數的調整.在定點數運算中，常常需要調整定點數的縮放係數，以下是一些可能的原因:若要將縮放係數"R"的定點數轉換為縮放係數"S"的定點數，整數需要乘以"R"/"S"。以1.23為例，若要從縮放係數"R"=1/100，轉換為縮放係數"S"=1/1000，整數123要乘以(1/100)/(1/1000) = 10，因此即為1230/1000。若二個縮放係數都是基數的次冪，調整縮放係數只是除去較低位數的數字，或是在較低位數增加零。不過，此一運算不能改變符號位元，因此需要像算術移位運算一樣的方式，延伸其符號位元。若"R"不能被"S"整除，產生的整數需要進行數值修約。若"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。而運算"r" ← "r"/"s"需要將整數相除，再乘以"S"，所得結果可能會有數值修約，也有可能會溢位。和浮點數的轉換.若要將浮點數轉換為定點數，可以將浮點數除以縮放係數"S"，再修約到最接近的整數。需確認對應的變數有足夠的大小儲存所得的結果。依縮放係數、變數長度，以及數值大小的不同，轉換有可能需要修約。若要將定點數轉換為浮點數，可以將定點數乘以縮放係數"S"。若整數的絕對值超過224的情形。硬體支援.比例和正規化.一般的處理器沒有特別支援定點數的運算，不過大部份二進位運算的處理器會有快速的位操作指令，因此可以在很短的時間進行和二的乘冪的乘法或除法，有些還有算術移位指令，在和二的乘冪相乘除的同時，還可以保留數字的正負號。早期像是或是Burroughs B3500的電腦會使用二進碼十進數表示法來處理整數，在十進位制下每一個位數會分別用四個次元來儲存，有些處理器還使用此一運算方式，因此可以用移位的方式來處理和十的乘冪相乘除的運算。有些DSP架構會支援特定的定點運算格式，例如有號的"n"位元數字，其中有"n"−1位是儲存小數，其中會有特別包括正規化的乘法指令，在相乘後將小數位數從2"n"−2位調整回"n"−1位。若DSP沒有支援此一機能，程式設計者需要用夠大的暫存器或是暫存變數來儲存此數值，再另外用程式進行正規化處理。溢位.若運算結果的數值太大，超過要儲存位置可以儲存的範圍，就會出現溢位。若是加法或是減法，所得結果會比運算數字多一個位元，若是乘法，所得結果的位元數會是二個運算數字位元數的和。若出現溢位，最高位元的資訊可能會因此而更改，若儲存空間有"n"位元，所儲存的會是除以2"n"的餘數，而最高位元會視為符號位元，因此結果的大小及正負號都可能改變。有些處理器有，或是在溢位時進行的異常處理。有些處理器則會有飽和運算，若相加或是相減的結果可能會溢位，考慮其正負號，儲存相同符號，絕對值最大的數值。，飽和運算只保留了正負號，無法保留結果的大小，在實務上可能不太實用。一般比較簡單及安全的作法是選擇適當的縮放係數以及變數儲存長度，避免溢位的可能性，或者在運算之前先檢查運算結果是否可能溢位。程式語言支援.有些程式語言明確支援定點數表示法及運算，著名的有PL/I、COBOL、Ada、JOVIAL及。這些程式語言中提供定點數的數值資料型別，可能是二進位制或十進位制，可以指定給變數或是函式。編譯器會在處理有關的程式碼時自動進行轉換，例如處理類似"r" ← "s" × "t" + "u"的程式碼，讀寫這些變數的值，或是將這些資料型別轉換為浮點數時。這類的程式語言多半是在1940年至1990年之間設計的，現今的程式語言多半都不提供定點數資料型別，也不提供相關的係數轉換。有些較古老，但仍很受歡迎的程式語言也是如此，例如Fortran、C語言和C++。浮點處理器的普及，以及嚴格標準化的行為，大幅的減少二進制定點數的需求。有些程式語言支援，也去除了大多數需要十進位制固點數的需求。很少數需要固點數的情形，會由程式設計師寫程式實現，並且有明確的係數轉換，這在任何程式語言都可以實現。另一方面，所有關聯式資料庫以及SQL表示法都支援用十進制定點數來進行數值的儲存以及運算。PostgreSQL有一種特別的型態，可以精確的儲存數值，位數最多可以到1000位。ISO在2008年針對C語言提出了有關定點數資料型態的擴充提案，目的為了方便程式在嵌入式處理器上執行。GCC的編譯器也支援定點數。具體範例.十進制定點數乘法.以下是二個三位小數的定點數乘法。=1*10.500 + 0.050*10.500= 10.500+0.525000=11.025000因為有三位小數，因此保留小數最後的0。為了要重整為整數乘法，先將數字都乘以1000，使數字都變成整數，之後再乘以formula_1二次，使最終結果不會改變，其算式如下若用其他的進位制，也可以用類似的原理來進行，因為二進位制的左移或右移都相當是數值的乘2或是除2。十進位制的三位數接近二進位制的十位數，因此可以將0.5表示為有十位小數的二進位制數字。最低的近似值是0.0000110011.乘法會變成若轉為十進位制，保留三位小數，會是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"是總位元數。