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

alf*_*lfC 52 c++ algorithm x86 cmath

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

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

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

std::pow(-1, n)
std::pow(-1, n%2)
(n%2?-1:1)
(1-2*(n%2))  // (gives incorrect value for negative n)
Run Code Online (Sandbox Code Playgroud)

编辑:

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

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

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

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

   1 - ((n & 1) << 1);
Run Code Online (Sandbox Code Playgroud)

(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
Run Code Online (Sandbox Code Playgroud)

   retvals[n&1];
Run Code Online (Sandbox Code Playgroud)

(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
Run Code Online (Sandbox Code Playgroud)

现在进行优化(-O3)

   1 - ((n & 1) << 1);
Run Code Online (Sandbox Code Playgroud)

(4操作,无内存访问)

  add edx, edx
  mov ebp, 1
  and edx, 2
  sub ebp, edx
Run Code Online (Sandbox Code Playgroud)

.

  retvals[n&1];
Run Code Online (Sandbox Code Playgroud)

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

  mov eax, edx
  and eax, 1
  movsx rcx, eax
  mov r12d, DWORD PTR main::retvals[0+rcx*4]
Run Code Online (Sandbox Code Playgroud)

.

   n%2?-1:1;
Run Code Online (Sandbox Code Playgroud)

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

  cmp eax, 1
  sbb ebx, ebx
  and ebx, 2
  sub ebx, 1
Run Code Online (Sandbox Code Playgroud)

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

结论(目前)

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

  • 1 - ((n & 1) << 1);总是好的,但不是非常传神.
  • retvals[n&1]; 为内存访问付出代价.
  • n%2?-1:1; 表现力很好,但只有优化.

Eli*_*nti 49

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

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

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

  • `n << 1`被C标准定义为'n*2`(对于非负`n`).,这是关于可读性而不是优化. (12认同)
  • 我想知道有多少人拥有8086处理器和8086时代的C编译器.建议使用位移代替整数乘以2而没有强烈的警告似乎很奇怪. (9认同)
  • 您是否要展示显示这种最快方式的基准测试? (7认同)
  • 一个好的优化编译器*可能*不能编译`((n&1)? - 1:1)`中的分支,但如果你能自己做,为什么不呢? (3认同)
  • @juanchopanza如果我有一个8086处理器和一个8086时代的C编译器,我会做,但是没有必要只看一下乘法与移位和整数余数与逻辑和的相比所需的周期数.对于当前的编译器和现代1周期乘法处理器来说,它要复杂得多. (2认同)

Guv*_*nte 36

通常你实际上并没有计算(-1)^n,而是跟踪当前的符号(作为一个数字是-1或者1)并且每次操作都将其翻转(sign = -sign),在处理你n的顺序时执行此操作,你将获得相同的结果.

编辑:请注意,我推荐这个的部分原因是因为实际上很少有语义值表示(-1)^n它只是一种在迭代之间翻转符号的方便方法.

  • 如果你可以_flip sign_:`sign = -sign;`,为什么要乘以`-1`? (9认同)
  • 没有必要跟踪任何东西.结果是`n`的一个非常简单的功能. (8认同)
  • +1这个答案提出了一个很好的观点!(-1)^ n中存在"很少的语义值",这是完全正确的,最重要的观察要做:) (8认同)

Fla*_*ken 7

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

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

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

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

最后建议@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;
}
Run Code Online (Sandbox Code Playgroud)