定點數運算

在電腦中，定點數（fixed-point number）是指用固定整數位數表達分數的格式，屬於實數數據類型中一種。例如美元常會表示到二位小數，以分來表示，即為一種定點數。有時定點數也會要求要有固定的整數位數。定點數與更複雜的浮點數相對。

在定點數表示法中，小數部份和整數部份一樣，也會表示為進制底數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以外的數字時，才能表示其精確值。

### Q格式

表示字長和二進位定點數中小數點位置的方法有很多種。下面的例子中，使用 f 表示小數部分位數、m 表示整數部分位數、s 表示符號位位數、b 表示總位數，其中「位數」均為比特位。

- Qf：稱作「Q 格式」，Q15 表示 15 個小數位。這樣的記法存在歧義，因為沒有包含字長，但實際使用時一般可以根據目標處理器平台判斷字長為 16 或者 32 位。
- Qm.f：無歧義的 Q 格式，因為整個字是補碼整數，所以符號位長度可以根據其它資訊推導而來。例如 Q1.30 表示該數有 1 個整數位、30 個小數位，是 32 位補碼整數。
- fxm.b：「fx 格式」與上述 Q 格式類似，區別在於點號後是字長。例如 fx1.16 表示有 1 個整數位、15 個小數位的 16 位字。
- s:m:f：PS2 中所使用的表示法，包含符號位資訊。0:8:0 表示 8 位無符號整數。

## 和浮點數的比較

定點數的運算比浮點數要快，需要的硬體也比較少。假如要計算的數值範圍事先就已知道，而且限制在較小的範圍內，定點數運算可以有效的使用其位元。例如，用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，後者的計算結果比較接近。

### 縮放係數的調整

在定點數運算中，常常需要調整定點數的縮放係數，以下是一些可能的原因:

- 將一數值存到不同縮放係數的變數中。
- 將二個數值轉換為相同的縮放係數，以便相加減或是比較。
- 一個數值在和其他數值相乘或是相除後，恢復成原來的縮放係數。
- 要增加除法運算的精度
- 要確保乘積或是商的縮放係數是像10n或2n，基數的次冪。
- 要確保運算結果可以在沒有溢位的情形下儲存在變數內。
- 為了要減少處理定點數硬體的成本。

若要將縮放係數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位。

ISO在2008年針對C語言提出了有關定點數資料型態的擴充提案，目的為了方便程式在嵌入式處理器上執行。GCC的編譯器也支援定點數。

## 具體範例

### 十進制定點數乘法

以下是二個三位小數的定點數乘法。

(10.500)(1.050)

=1*10.500 + 0.050*10.500

= 10.500+0.525000=11.025000

因為有三位小數，因此保留小數最後的0。為了要重整為整數乘法，先將數字都乘以1000，使數字都變成整數，之後再乘以$1/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是總位元數。

- COBOL程式語言一開始支援任意大小以及小數位數的十進制定點數，其格式會用`PIC`命令功能（directive）來標示。例如`PIC S9999V99`代表六位數整數，二位小數的的十進制有號數。
- PL/I程式語言使用`REAL FIXED BINARY (`p`,`f`)`的construct來標示，標示二進制定點數，其中共有p位數（不考慮符號），其中小數是f位數，總共的有號整數有p+1位數，其轉換係數是1/2f。也可以用`COMPLEX` 代替`REAL`，用`DECIMAL`代替`BINARY`來表示十進制。
- 在Ada程式語言中，數值型態的表示法會像`type F is delta 0.01 range -100.0 .. 100.0`，表示用二補數格式的有號二進制表示法，其中小數位數是7位元（縮放係數是1/128），至少要用15位元來儲存（確保實際範圍可以從-128.00到將近+128.00）。
- Q格式是由德州儀器定義的。用`Q`f來表示有f位小數的有號定點數，例如`Q15`是縮放係數為1/215的二補數有號數。而 `Q`m`.`f額外的標示數字的整數部份有m位（不算符號位元）。因此`Q1.30`表示有一位整數，30位小數的二進制定點數，可以存在32位元二補數整數中，縮放係數是1/230。ARM架構也有類似的標示法，但其中的m有包括符號位元，因此上述的格式要改為`Q2.30`。
- VisSim公司用`fx`m`.`b來表示共有b位元，小數有m位元的二進制定點數，因此是b位元的整數，縮放係數是1/2b−m。因此`fx1.16`是十六位元數字，整數部份1位元，小數部份15位元。
- PlayStation 2 GS（Graphics Synthesizer）使用者指南中用s`:`m`:`f的標示方式，其中s表示是否有符號位元。例如，`0:5:3`表示8位元整數，縮放係數是1/23的無號數。
- LabVIEW程式語言的標示方式是`<`s`,`b`,`m`>`，表示FXP定點數的格式。s可以是'+'或'±'，表示無號數或是二補數的有號數。b是總位元個數，而m是整數部份的位數。

## 軟體應用例

- 常用的TrueType字型格式，在一些指令中，使用32位元的有號二進制定點數，來表達數值。這個格式是在考慮字體微調以及效能下，可以提供最小精度的格式。
- Sony原生PlayStation和世嘉土星的所有3D計算引擎、任天堂的Game Boy Advance（其中的二維電腦圖形）、任天堂DS（2D和3D）、任天堂GameCube及GP2X Wiz電子遊戲系統，這些系統沒有浮點運算器，都使用定點運算。PlayStation中的轉換協處理器中有硬體支援16位元的浮點運算，其中有12位的小數。
- 廣為科學界和數學界使用的TeX軟體，在所有位置的運算中，使用32位元有號二進制定點數運算，其小數位數是16位。這個值的單位長度對應印刷中的點。TeX font metric檔案使用32位元定點數，其中有12位小數。
- Tremor (軟體)、Toast (GSM)和MPEG聲音解碼器都是軟體的函式庫，可以針對Vorbis、Full Rate和MP3的聲音檔。這些解碼都使用定點運算，因為許多解碼硬體中沒有浮點運算器。
- WavPack無損聲音壓縮器也是使用定點運算。此決定有一部份是擔心不同硬體中對於浮點數的捨入規則差異會破壞壓縮器的無損特性。
- Nest Labs Utilities library提供少量針對定點數的巨集以及函數，特別是用在處理和感測器取樣以及感測器輸出有關的數字。
- OpenGL ES 1.x規格中包括定點數，因為這是針對嵌入式系統的API，不一定有浮點運算器。
- Dc和Bc程式都是高精度計算計算器，但只支擾（使用者定義的）定點小數。
- Fractint表達數字的方式類似Q格式的定點數，可以加速在Intel 80386或Intel 80486SX等沒有浮點運算器的舊電腦。
- 毀滅戰士是Id Software所出的第一人稱射擊遊戲，其非整數運算（例如地圖系統、地理、算繪、玩家移動等）都是用16.16的定點格式。為了相容性考量，此表達法仍用在現代的Doom source port中。
- 有時在儲存或處理影像或是圖像幀時，會使用定點數。有單指令流多數據流單元，主要針對影像處理的處理器，一般都有適用於定點數的指令。
- Microsoft Azure量子計算機的Q Sharp程式語言，是實現量子閘用的語言，其中包括標準的數值函數庫，可以在量子位元暫存器上處理定點運算。