为什么编译器不会将浮点数* 2优化为指数增量?

我经常注意到gcc把乘法转换成可执行文件的转换。 乘以一个int和一个float可能会发生类似的情况。 例如, 2 * f可以简单地将f的指数递增1,从而节省一些周期。 做编译器,也许如果有人要求他们这样做的话(比如通过-ffast-math ),一般来说,这样做吗?

编译器是否足够聪明可以做到这一点,或者我需要使用scalb*()ldexp()/frexp()函数族自己来做这scalb*()吗?

例如,2 * f可以简单地将f的指数递增1,从而节省一些周期。

这根本不是事实。

首先,你有太多的angular落案例,如零,无穷,楠,和denormals。 那么你有性能问题。

误解是增加指数并不比乘法快。

如果你看看硬件指令,没有直接的方法来增加指数。 所以你需要做的是:

  1. 按位转换为整数。
  2. 增加指数。
  3. 按位转换回浮点。

在整数执行单元和浮点执行单元之间移动数据通常存在中等到较大的延迟。 所以最后这个“优化”比简单的浮点乘法要糟得多。

所以编译器之所以不做这个“优化”,是因为它没有更快。

在现代CPU上 ,乘法通常具有每周期一次的吞吐量和较低的延迟。 如果这个值已经在一个浮点寄存器中了,那么你就不可能通过在这个expression式上进行整数运算来绕过它。 如果它开始于内存中,并且假设既不是当前值也不是正确的结果将是零,denormal,nan或infinity,那么执行类似于

 addl $0x100000, 4(%eax) # x86 asm example 

乘以二; 我唯一能看到这个好处的是,如果你正在操作一整组浮点数据,这些数据的范围是零和无穷大的,那么你将要执行的操作是唯一的您没有任何理由将数据加载到浮点寄存器中)。

常见的浮点格式,特别是IEEE 754,不把指数存储为一个简单的整数,将其作为一个整数处理,不会产生正确的结果。

在32位浮点型或64位双精度型中,指数字段分别为8位或11位。 指数代码1到254(浮点型)或1到2046(双精度型)的行为与整数相同:如果将这些值中的一个添加到其中一个值,并且结果是其中一个值,则表示的值将加倍。 但是,在这些情况下添加一个失败:

  • 初始值为0或低于正常值。 在这种情况下,指数字段从零开始,并且向其添加一个将数字加2到-126 (浮点数)或2 -1022 (双数); 它不会增加一倍。
  • 初始值超过2 127 (浮点)或2 1023 (双倍)。 在这种情况下,指数字段从254或2046开始,并且向其添加一个将该数字改变为NaN; 它不会增加一倍。
  • 初始值是无穷大或NaN。 在这种情况下,指数字段从255或2047开始,向其添加一个将其更改为零(并且可能溢出到符号位中)。 结果是零或低于正常值,但应分别为无穷大或NaN。

(以上是积极的迹象,情况是对称的,有负面的迹象)

正如其他人所指出的那样,一些处理器没有快速处理浮点值位的设施。 即使是那些做的,指数字段也不是与其他位隔离的,所以你通常不能把它添加到它,而不会溢出到上面最后一种情况的符号位。

虽然有些应用程序可以容忍快捷方式,例如忽略子exception或NaN甚至无限,但应用程序可以忽略零。 由于向指数添加1无法正确处理零,因此不可用。

这不是编译器或编译器编写者不聪明。 更像是服从标准,产生所有必要的“副作用”,如Infs,Nans和Denormals。

也可以是产生其他不被要求的副作用,如阅读记忆。 但是我确实认识到,在某些情况下可能会更快。

实际上,这是硬件中发生的事情。

2也作为浮点数传递给FPU,尾数为1.0,指数为2 ^ 1。 对于乘法,指数被添加,并且曼狄萨斯相乘。

假设有专门的硬件来处理复杂的情况(乘以不是2的幂的值),并且特殊情况的处理不会比使用专用硬件更差,那么就没有必要拥有额外的电路和指令。

对于embedded式系统编译器来说,可能有一个特殊的逐比例伪操作,可以由代码生成器以任何最适合于所讨论的机器的方式进行转换,因为在某些embedded式处理器上,指数可能比完全乘以2的乘法快一个数量级,但是在乘法最慢的embedded式微处理器上,编译器可能通过使用浮点乘法例程检查它的参数来获得更大的性能提升在运行时间,以便跳过零的尾数部分。

之前有关乘以2的乘方的Stackoverflow问题 。 共识和实际实施certificate,不幸的是,目前没有比标准的乘法更高效的方法。

如果你认为乘以二意味着将指数增加1,那么再想一想。 以下是IEEE 754浮点运算的可能情况:

案例1:无穷大和NaN保持不变。

情况2:通过增加指数并将除符号位之外的尾数设置为零来将具有最大可能指数的浮点数改变为无穷大。

情况3:指数小于最大可能指数的归一化浮点数的指数增加1。 yippee的!

情况4:具有最高尾数位的非标准化浮点数的指数增加1,将其转换为标准化数。

情况5:清除了最高尾数位的非标准化浮点数(包括+0和-0),其尾数向左移一位,保持指数不变。

我非常怀疑生成正确处理所有这些情况的整数代码的编译器会像处理器内置的浮点一样快。 它只适用于乘以2.0。 乘以4.0或0.5,适用一套全新的规则。 对于乘以2.0的情况,你可以尝试用x + x代替x * 2.0,许多编译器都这样做。 就是他们这样做,因为处理器可能能够例如同时进行一次加法和一次乘法,而不是每种加法。 所以有时候你会更喜欢x * 2.0,有时x + x,这取决于其他操作需要同时进行的操作。