定點數運算

在電腦中，定点数（）是指用固定整數位數表達分數的格式，屬於实数数据类型中一種。例如美元常會表示到二位小數，以分來表示，即為一種定点数。有時定点数也會要求要有固定的整數位數。定点数与更复杂的浮点数相对。

在定点数表示法中，小數部份和整數部份一樣，也會表示為進制底数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 表示总位数，其中「位数」均为比特位。

 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系列的協同處理器，以及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|非常大或是非常小，可能會有溢位或算术下溢（小於可以表示的最小數）的情形。

硬體支援

比例和正規化
一般的處理器沒有特別支援定點數的運算，不過大部份二進位運算的處理器會有快速的位操作指令，因此可以在很短的時間進行和二的乘幂的乘法或除法，有些還有算术移位指令，在和二的乘幂相乘除的同時，還可以保留數字的正負號。

早期像是或是Burroughs B3500的電腦會使用二進碼十進數（BCD）表示法來處理整數，在十進制下每一個位數會分別用四個次元來儲存，有些處理器還使用此一運算方式，因此可以用移位的方式來處理和十的乘幂相乘除的運算。

有些DSP架構會支援特定的定點運算格式，例如有號的n位元數字，其中有n−1位是儲存小數（其儲存數值範圍從-1到幾乎是+1的），其中會有特別包括正規化的乘法指令，在相乘後將小數位數從2n−2位調整回n−1位。若DSP沒有支援此一機能，程式設計者需要用夠大的暫存器或是暫存變數來儲存此數值，再另外用程式進行正規化處理。

溢位
若運算結果的數值太大，超過要儲存位置可以儲存的範圍，就會出現溢位。若是加法或是減法，所得結果會比運算數字多一個位元，若是乘法，所得結果的位元數會是二個運算數字位元數的和。

若出現溢位，最高位元的資訊可能會因此而更改，若儲存空間有n位元，所儲存的會是除以2n的餘數，而最高位元會視為符號位元，因此結果的大小及正負號都可能改變。

有些處理器有，或是在溢位時進行的异常处理。有些處理器則會有饱和运算，若相加或是相減的結果可能會溢位，考慮其正負號，儲存相同符號，絕對值最大的數值。，

饱和运算只保留了正負號，無法保留結果的大小，在實務上可能不太實用。一般比較簡單及安全的作法是選擇適當的缩放系数以及變數儲存長度，避免溢位的可能性，或者在運算之前先檢查運算結果是否可能溢位。

程式語言支援
有些程式語言明確支援定點數表示法及運算，著名的有PL/I、COBOL、Ada、JOVIAL及。這些程式語言中提供定點數的數值資料類型，可能是二進制或十進制，可以指定給變數或是函式。編譯器會在處理有關的程式碼時自動進行轉換，例如處理類似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.52500011.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是總位元數。

 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格式是由德州仪器定義的。用Qf來表示有f位小數的有號定點數，例如Q15是缩放系数為1/215的二補數有號數。而 Qm.f額外的標示數字的整數部份有m位（不算符號位元）。因此Q1.30表示有一位整數，30位小數的二進制定點數，可以存在32位元二補數整數中，缩放系数是1/230。ARM架構也有類似的標示法，但其中的m有包括符號位元，因此上述的格式要改為Q2.30。
 公司用fxm.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位小數。
 、和都是軟體的函式庫，可以針對Vorbis、和MP3的聲音檔。這些解碼都使用定點運算，因為許多解碼硬體中沒有浮點運算器。
 WavPack無損聲音壓縮器也是使用定點運算。此決定有一部份是擔心不同硬體中對於浮點數的捨入規則差異會破壞壓縮器的無損特性。
 Nest Labs Utilities library提供少量針對定點數的巨集以及函數，特別是用在處理和感測器取樣以及感測器輸出有關的數字。
 OpenGL ES 1.x規格中包括定點數，因為這是針對嵌入式系統的API，不一定有浮點運算器。
 Dc和Bc程式都是高精度计算計算器，但只支擾（使用者定義的）定點小數。
 表達數字的方式類似Q格式的定點數，可以加速在Intel 80386或Intel 80486SX等沒有浮點運算器的舊電腦。
 毀滅戰士是Id Software所出的第一人称射击游戏，其非整數運算（例如地圖系統、地理、算繪、玩家移動等）都是用16.16的定點格式。為了相容性考量，此表達法仍用在現代的Doom source port中。
 有時在儲存或處理影像或是圖像幀時，會使用定點數。有单指令流多数据流單元，主要針對影像處理的處理器，一般都有適用於定點數的指令。
 Microsoft Azure量子计算机的Q Sharp程式語言，是實現量子閘用的語言，其中包括標準的數值函數庫，可以在量子位元暫存器上處理定點運算。

相關條目 
 Q格式
 浮点数
 计算机编码
 误差、数值修约
 ：C語言的定點數函式庫
 
 
 
 模除

参考文献 

計算機算術
数据类型