浮点数与定点数:优缺点是什么?

25

浮点类型通过将有效数字和指数分别存储在不同的二进制字中,使数字适应16、32、64或128位。

定点类型使用2个字存储数字,一个表示整数部分,另一个表示小数点后的部分,使用负指数,如2^-1、2^-2、2^-3等。

浮点数在指数意义上具有更广泛的范围,因此更好,但如果要在某个范围内存储更精确的数字,例如仅使用-16到16之间的整数,就需要使用更多位来容纳小数点后的数字。

就性能而言,哪种性能最佳?还是有一些情况其中一种比另一种更快吗?

在视频游戏编程中,是否每个人都使用浮点数,因为FPU使其更快,或者性能下降只是可以忽略不计,还是他们自己创建了自己的固定类型?

为什么C/C++中没有任何固定类型?


2
我的印象是,除了FPU使浮点数非常快之外,定点运算应该更快。而且,除了整数之外,定点运算在语言和CPU方面的支持不太多,因为它不太灵活,并且可以使用整数进行相对容易的模拟。但我在这个领域不是专家。 - LarsH
2
你对定点数的表示方式的理解是错误的。 - Oliver Charlesworth
@oli:该死!我快要失控了! - jokoon
@oli:例如数字1.3,有两个单词0x1和0x3,我没错吧? - jokoon
2
在定点数中,分母在软件中被隐式硬编码(也就是说,所有处理这些数字的代码都假定有一个固定的指数/分母)。例如,如果分母是2^16,则1.30十进制表示为85196/65536。分母不会被存储。请注意,这种表示仍然是不精确的(1.300 == 85196.800 / 65536.000)。它也不是浮点数。 - rwong
1
@jokoon,你的例子是BCD编码,但不是定点数。 - tofro
8个回答

11

这个定义仅适用于固定点实现的非常有限的子集。

更准确地说,在固定点中只存储尾数,指数是预先确定的常量。没有要求二进制点必须落在尾数内部,也绝对没有要求它落在字边界上。例如,以下所有内容都是“固定点”:

  • 64位尾数,按2-32缩放(这符合问题中列出的定义)
  • 64位尾数,按2-33缩放(现在无法通过八位字节边界分离整数和小数部分)
  • 32位尾数,按24缩放(现在没有小数部分)
  • 32位尾数,按2-40缩放(现在没有整数部分)

GPU通常使用没有整数部分的固定点(通常是32位尾数,按2-32缩放)。因此,OpenGL和Direct3D等API通常使用能够容纳这些值的浮点类型。但是,操作整数尾数通常更有效率,因此这些API也允许以这种方式指定坐标(在纹理空间、颜色空间等中)。

至于你声称C++没有固定点类型的说法,我不同意。在C++中,所有整数类型都是固定点类型。指数通常被假定为零,但这不是必需的,我已经用这种方式在C++中实现了相当多的固定点DSP代码。


3
整数和真正的定点类型之间的区别在于,使用整数进行定点运算时,您必须在乘法或除法操作后移动结果。内置的定点类型会在底层执行该操作。在定点 DSP 硬件上,指令集直接支持此操作,但如果没有这种硬件,固定点 * 和 / 操作比相应的普通整数操作要慢。 - Clifford
@Oli: 从DirectX9开始,它们就被纳入其中了;这是DirectX9规范的一部分。 - Clifford
1
你可以在每次乘法或除法后移动结果,但这并不一定是一个好主意。通常最好让(隐藏的)结果指数与参数不同,最后再进行移位。有时移位可能永远不会必要(例如,如果你只是想将结果与常量进行比较,可以更改常量的指数以匹配而无需运行时移位)。此外,在许多体系结构中,按照某些间隔移位是免费的,通过将例如AH或AL寄存器存储到内存中而不是EAX。 - Ben Voigt
1
还有另一种非常流行的定点实现:无限位小数尾数,按10^-2缩放。也就是所谓的“货币”。显然与游戏编程无关(至少不涉及图形部分),但却是最广泛使用的定点数据类型之一,对于大型多人在线角色扮演游戏来说非常重要。 - Jörg W Mittag
@Jörg:实际上,至少对于“欧元”货币来说,规定应该使用5位数字进行操作(以避免过多的舍入误差),然后在最后使用数学舍入将其四舍五入为2位数字。此外,许多货币根本没有任何数字(例如日元,自1954年以来就没有使用过细分)。 - Matthieu M.
@Jorg:说得好,尽管计算机实现货币数据类型通常使用10<sup>-4</sup>缩放的64位尾数。对于某些游戏来说,无限数字表示实际上可能更有用,但64位对于可预见的未来来说已经足够了。 - Ben Voigt

5
在代码级别上,定点算术就是带有隐含分母的整数算术。
对于许多简单算术运算,定点和整数运算本质上是相同的。然而,有一些操作需要用更高位数的中间值来表示,然后舍入。例如,要将两个16位定点数相乘,结果必须在重新规范化(或饱和)回到16位定点之前暂时存储在32位中。
当软件没有利用矢量化(如基于CPU的SIMD或GPGPU)时,整数和定点算术比FPU更快。当使用向量化时,向量化的效率更加重要,因此定点和浮点之间的性能差异无关紧要。
某些体系结构为某些数学函数提供硬件实现,例如sin、cos、atan、sqrt,仅适用于浮点类型。有些架构根本不提供任何硬件实现。在这两种情况下,专门的数学软件库可以通过仅使用整数或定点算术来提供这些功能。通常,这样的库将提供多个精度级别,例如,只精确到N位精度的答案,这比表示的完全精度要低。有限精度版本可能比最高精度版本更快。

4

定点数在DSP和嵌入式系统中被广泛使用,因为目标处理器通常没有FPU,而且可以使用整数ALU相对高效地实现定点数。

就性能而言,这可能会因目标架构和应用程序而异。显然,如果没有FPU,则定点数将快得多。当有FPU时,这也将取决于应用程序。例如,执行一些函数(如sqrt()或log())时,直接在指令集中支持要比通过算法实现快得多。

在C或C++中没有内置的定点数类型,我想是因为它们(至少是C)被设想为系统级语言,需要定点数有点特定领域,而且也许是因为在通用处理器上通常没有直接的硬件支持定点数。

在C++中,定义一个具有适当的运算符重载和相关数学函数的定点数据类型类可以轻松地克服这个缺点。但是,对于这个问题,有好的和坏的解决方案。 好的示例可以在此处找到:http://www.drdobbs.com/cpp/207000448。该文章中的代码链接已经失效,但我找到了它:ftp://66.77.27.238/sourcecode/ddj/2008/0804.zip


你提供的源代码链接也失效了(至少在我写这篇文章时是这样),但我已经在原作者所在公司的网站上找到了它:http://www.justsoftwaresolutions.co.uk/files/fixed_source.zip - Gavin
请注意,我在log()函数中发现了一个bug,其中一个查找表的值短了一个。作者已经验证了这一点,但看起来这些文件还没有更新。我会在找到细节后发布更正信息。 - Clifford

2
在这个上下文中讨论“精度”时需要小心。对于相同位数的表示,最大的定点值比任何浮点值具有更多的有效位数(因为浮点格式必须放弃一些位数给指数),但是最小的定点值比任何非规格化浮点值少(因为定点值在前导零中浪费了大部分尾数)。此外,根据您分割定点数的方式,浮点值可能能够表示更小的数字,这意味着它具有更精确的“微小但非零”的表示。等等。

1
您在游戏中不使用浮点数是因为它更快或更慢,而是因为使用浮点数实现算法比使用定点数更容易。您认为原因与计算速度有关,但实际上是编程的便利性。
例如,您可以将屏幕/视口的宽度定义为从0.0到1.0,屏幕的高度为0.0到1.0,单词的深度为0.0到1.0等。矩阵数学等使实现变得非常容易。在需要计算实际像素大小的真实屏幕尺寸(如800x400)之前,以这种方式进行所有数学运算。从眼睛到世界上对象上的点投射光线,并计算它穿过屏幕的位置,使用0到1的数学方法,然后将x乘以800,y乘以400并放置该像素。
浮点数不会分别存储指数和尾数,尾数是一个古怪的数字,即指数和符号之后剩余的数字,例如23位,而不是16位、32位或64位。
浮点数运算在其核心上使用固定点逻辑,需要额外的逻辑和步骤。相比之下,固定点数学更便宜,因为你不必在进入ALU时操作数据,也不必在出来时操作数据(规范化)。当你加入IEEE及其所有垃圾时,会增加更多的逻辑、时钟周期等(正确的有符号无穷大、静默和信令NaN、如果启用异常处理程序,则对同一操作产生不同的结果)。正如有人在评论中指出的,在一个真正的系统中,你可以并行地进行固定和浮点运算,这样就可以利用一些或所有的处理器,并通过这种方式恢复一些时钟。使用大量芯片实际面积可以增加固定和浮点时钟速率,固定将保持更便宜,但是使用这些技巧以及并行操作,浮点可以接近固定速度。

1

浮点数和整数运算的区别取决于您考虑的CPU。在英特尔芯片上,时钟周期的差异不大。整数运算仍然更快,因为有多个整数ALU可以并行工作。编译器也很聪明,使用特殊的地址计算指令来优化单个指令中的加法/乘法。转换也算作一种操作,所以只需选择您的类型并坚持使用它。

在C++中,您可以构建自己的固定点数类型。您只需定义一个带有一个int的结构体,并覆盖适当的重载,使它们执行通常的操作加上移位以将逗号放回正确的位置。


实际上,整数运算并不是最快的。将工作分散到整数ALU、FPU和SIMD单元中是最快的,但显然更加复杂。 - Ben Voigt
@Ben:没错,但这超出了这个问题的范围。 - user180326

0
一个未涉及的问题是答案是功耗。虽然这高度取决于具体的硬件架构,但通常FPU在CPU中消耗的能量比ALU多得多,因此如果你的目标是移动应用程序,其中功耗很重要,那么考虑使用算法的定点实现是值得的。

1
我想知道你是否能够设计软件,使其在浮点算法和定点算法之间轻松移植。到目前为止,我认为这并不容易... - jokoon
其实很简单。在大多数情况下,你只需要使用 'typedef int32 fixed' 和 '#define MUL(a,b) ((int32)((int64)(a) * (b)) >> radix)' 就可以开始了。 - Nikolay Shmyrev

-1

这取决于你正在处理什么。如果你使用定点数,则会失去精度;你必须选择小数点后的位数(这可能并不总是足够好)。在浮点数中,你不需要担心这个问题,因为所提供的精度几乎总是足够完成手头的任务 - 使用标准形式实现来表示数字。

利弊归结为速度和资源。在现代32位和64位平台上,真的没有必要使用定点数。大多数系统都配备了内置的FPU,这些FPU被硬连线以优化定点操作。此外,大多数现代CPU内部指令都带有操作,例如SIMD集合,通过向量化和展开来帮助优化基于向量的方法。因此,定点数只有一个缺点。

在嵌入式系统和小型微控制器(8位和16位)上,你可能没有FPU或扩展指令集。在这种情况下,你可能被迫使用定点数方法或有限的浮点指令集,这些指令集不是很快。因此,在这些情况下,定点数将是更好的选择 - 或者甚至是你唯一的选择。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接