定点数运算

在电脑中，定点数（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"，可以表示的最大值和最小值"V"min和"V"max，以及其精度"δ" = "S"/2。
精确值.
在二进制的定点数中，考虑二进制下的分数"a"/2"m"（例如1/16或17/32），若其缩放系数为1/2"n"，且"n" ≥ "m"，即可以精确的用二进制定点数表示。不过大部份的十进制小数（如0.1或0.123）在二进制下会是无穷循环小数，因此无法用二进制表示其精确值。
十进制的情形也类似，考虑十进制下的分数"a"/10"m"（例如1/100或37/1000）若其缩放系数为1/10"n"，且"n" ≥ "m"，即可以精确的用十进制定点数表示。若分母是二的次幂的分数"a"/2"m"（如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"|非常大或是非常小，可能会有溢位或算术下溢（小于可以表示的最小数）的情形。
硬体支援.
比例和正规化.
一般的处理器没有特别支援定点数的运算，不过大部份二进位运算的处理器会有快速的位操作指令，因此可以在很短的时间进行和二的乘幂的乘法或除法，有些还有算术移位指令，在和二的乘幂相乘除的同时，还可以保留数字的正负号。
早期像是或是Burroughs B3500的电脑会使用二进码十进数（BCD）表示法来处理整数，在十进制下每一个位数会分别用四个次元来储存，有些处理器还使用此一运算方式，因此可以用移位的方式来处理和十的乘幂相乘除的运算。
有些DSP架构会支援特定的定点运算格式，例如有号的"n"位元数字，其中有"n"−1位是储存小数（其储存数值范围从-1到几乎是+1的），其中会有特别包括正规化的乘法指令，在相乘后将小数位数从2"n"−2位调整回"n"−1位。若DSP没有支援此一机能，程式设计者需要用够大的暂存器或是暂存变数来储存此数值，再另外用程式进行正规化处理。
溢位.
若运算结果的数值太大，超过要储存位置可以储存的范围，就会出现溢位。若是加法或是减法，所得结果会比运算数字多一个位元，若是乘法，所得结果的位元数会是二个运算数字位元数的和。
若出现溢位，最高位元的资讯可能会因此而更改，若储存空间有"n"位元，所储存的会是除以2"n"的余数，而最高位元会视为符号位元，因此结果的大小及正负号都可能改变。
有些处理器有，或是在溢位时进行的异常处理。有些处理器则会有饱和运算，若相加或是相减的结果可能会溢位，考虑其正负号，储存相同符号，绝对值最大的数值。，
饱和运算只保留了正负号，无法保留结果的大小，在实务上可能不太实用。一般比较简单及安全的作法是选择适当的缩放系数以及变数储存长度，避免溢位的可能性，或者在运算之前先检查运算结果是否可能溢位。
程式语言支援.
有些程式语言明确支援定点数表示法及运算，著名的有PL/I、COBOL、Ada、JOVIAL及。这些程式语言中提供定点数的数值资料类型，可能是二进制或十进制，可以指定给变数或是函式。编译器会在处理有关的程式码时自动进行转换，例如处理类似"r" ← "s" × "t" + "u"的程式码，读写这些变数的值，或是将这些资料类型转换为浮点数时。
这类的程式语言多半是在1940年至1990年之间设计的，现今的程式语言多半都不提供定点数资料类型，也不提供相关的系数转换。有些较古老，但仍很受欢迎的程式语言也是如此，例如Fortran、C语言和C++。浮点处理器的普及，以及严格标准化的行为，大幅的减少二进制定点数的需求。有些程式语言支援（例如C♯和Python），也去除了大多数需要十进制固点数的需求。很少数需要固点数的情形，会由程式设计师写程式实现，并且有明确的系数转换，这在任何程式语言都可以实现。
另一方面，所有关联式资料库以及SQL表示法都支援用十进制定点数来进行数值的储存以及运算。PostgreSQL有一种特别的<samp>numeric</samp>型态，可以精确的储存数值，位数最多可以到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"是总位元数。