获得(-1)^ n的正确方法是什么?

时间:2015-03-17 22:14:36

标签: c++ algorithm x86 cmath

许多算法需要计算(-1)^n(两者都是整数),通常作为系列中的因子。也就是说,奇数n为-1,偶数n为1。在C ++环境中,人们常常看到:

#include<iostream>
#include<cmath>
int main(){
   int n = 13;
   std::cout << std::pow(-1, n) << std::endl;
}

什么是更好或通常的惯例?(或其他),

std::pow(-1, n)
std::pow(-1, n%2)
(n%2?-1:1)
(1-2*(n%2))  // (gives incorrect value for negative n)

编辑:

此外,用户@SeverinPappadeux提出了另一种基于(全局?)数组查找的替代方案。我的版本是:

const int res[] {-1, 1, -1}; // three elements are needed for negative modulo results
const int* const m1pow = res + 1; 
...
m1pow[n%2]

这可能不会解决问题,但是,通过使用发出的代码,我们可以放弃一些选项。

首先没有优化,最终的竞争者是:

   1 - ((n & 1) << 1);

(7次操作,无内存访问)

  mov eax, DWORD PTR [rbp-20]
  add eax, eax
  and eax, 2
  mov edx, 1
  sub edx, eax
  mov eax, edx
  mov DWORD PTR [rbp-16], eax

   retvals[n&1];

(5个操作,内存 - 寄存器? - 访问)

  mov eax, DWORD PTR [rbp-20]
  and eax, 1
  cdqe
  mov eax, DWORD PTR main::retvals[0+rax*4]
  mov DWORD PTR [rbp-8], eax

现在进行优化(-O3)

   1 - ((n & 1) << 1);

(4次操作,无内存访问)

  add edx, edx
  mov ebp, 1
  and edx, 2
  sub ebp, edx

  retvals[n&1];

(4个操作,内存 - 寄存器? - 访问)

  mov eax, edx
  and eax, 1
  movsx rcx, eax
  mov r12d, DWORD PTR main::retvals[0+rcx*4]

   n%2?-1:1;

(4次操作,无内存访问)

  cmp eax, 1
  sbb ebx, ebx
  and ebx, 2
  sub ebx, 1

测试是here。我不得不使用一些杂技来获得有意义的代码,这些代码并不能完全消除操作。

结论(暂时)

所以最后它取决于水平优化和表现力:

  • 1 - ((n & 1) << 1); 始终好但不富有表现力。
  • retvals[n&1];为内存访问付出了代价。
  • n%2?-1:1;表现力很强,但只有优化。

7 个答案:

答案 0 :(得分:48)

如果您想要超级迂腐,我可以使用(n & 1)代替n % 2<< 1代替* 2,我的意思是优化。
因此,在8086处理器中计算的最快方法是:

1 - ((n & 1) << 1)

我只想澄清这个答案的来源。最初的海报alfC在发布许多不同的计算方法方面做得非常出色(-1)^ n有些方法比其他方式更快。<​​br/> 如今处理器的速度和它们一样快,并且优化编译器就像它们一样好,我们通常重视通过从操作中削减一些CPU周期的轻微(甚至可以忽略的)改进的可读性。
有一段时间,一个通过编译器统治地球,MUL操作是新的和颓废的;在那些日子里,2次操作的力量是无偿优化的邀请。

答案 1 :(得分:37)

通常您实际上并未计算(-1)^n,而是跟踪当前符号(数字为-11)并在每次操作时将其翻转({{ 1}}),在按顺序处理sign = -sign时执行此操作,您将获得相同的结果。

编辑:请注意,我推荐的部分原因是因为实际上语义值很少是表示n它只是在迭代之间翻转符号的便捷方法。

答案 2 :(得分:7)

首先,我知道最快的isOdd测试(在内联方法中)

/**
* Return true if the value is odd
* @value the value to check
*/
inline bool isOdd(int value)
{
return (value & 1);
}

然后使用此测试返回-1如果奇数,则返回1(这是(-1)^ N的实际输出)

/**
* Return the computation of (-1)^N
* @n the N factor
*/
inline int minusOneToN(int n)
{
return isOdd(n)?-1:1;
}

最后建议@Guvante,你可以省略乘法只是翻转一个值的符号(避免使用minusOneToN函数)

/**
* Example of the general usage. Avoids a useless multiplication
* @value The value to flip if it is odd
*/
inline int flipSignIfOdd(int value)
{
return isOdd(value)?-value:value;
}

答案 3 :(得分:3)

  

许多算法需要计算(-1)^ n(两个整数),通常为a   一系列因素。也就是说,奇数n为1的因子为1   甚至n。

考虑将系列评估为-x的函数。

答案 4 :(得分:2)

如果你需要它的速度,这里就是......

int inline minus_1_pow(int n) {
    static const int retvals[] {1, -1}; 
    return retvals[n&1];
}

优化转向11的Visual C ++编译器将其编译为两个机器指令,这两个指令都不是分支。它还优化了retvals数组,因此没有缓存丢失。

答案 5 :(得分:1)

怎么样?
(1 - (n%2)) - (n%2)

n%2最有可能只计算一次

更新

实际上,最简单,最正确的方法是使用表格

const int res[] {-1, 1, -1};

return res[n%2 + 1];

答案 6 :(得分:1)

如果我们在一系列中执行计算,为什么不在正循环和负循环中处理计算,完全跳过评估?

泰勒级数展开以近似(1 + x)的自然对数是这类问题的一个完美例子。每个项具有(-1)^(n + 1)或(1)^(n-1)。无需计算此因子。您可以通过为每两个术语执行1个循环或两个循环来“切片”问题,一个用于奇数项,一个用于偶数项。

当然,由于计算本质上是一个超过实数域的计算,因此无论如何,您将使用浮点处理器来评估各个术语。一旦你决定这样做,你应该只使用库实现自然对数​​。但是如果由于某种原因,你决定不这样做,那肯定会更快,但不会太多,不会浪费周期来计算-1到第n次幂的值。

也许每个人甚至可以在不同的线程中完成。也许问题可以被矢量化,甚至。