不使用abs函数,也不使用语句,获得绝对值

我想如何得到一个整数的绝对值,而不使用if语句或abs() 。 起初,我使用了左移位( << ),试图将负号从范围中移出,然后将位移回到原来的位置,但是不幸的是,它不适用于我。 请让我知道为什么它不工作和其他替代方法来做到这一点。

从Bit Twiddling黑客 :

 int v; // we want to find the absolute value of v unsigned int r; // the result goes here int const mask = v >> sizeof(int) * CHAR_BIT - 1; r = (v + mask) ^ mask; 

无网格*

 int abs (int n) { const int ret[2] = { n, -n }; return ret [n<0]; } 

注意4.7积分转换/ 4: [...] If the source type is bool, the value false is converted to zero and the value true is converted to one.


* :在你的代码中没有条件分支。 在引擎盖下,三元运算符也会产生一个分支。 然而,这也是一个有效的答案,因为三元不是一个if语句。 这并不意味着您的编译器无法为逻辑分支的代码发出无分支汇编代码。

 int abs(int v) { return v * ( (v<0) * (-1) + (v>0)); // simpler: v * ((v>0) - (v<0)) thanks Jens } 

此代码将v的值与-11相乘以得到abs(v)。 因此,括号内将是-11

如果v是正数,则expression式(v>0)为真,并且当(v<0)为假时(假值为0 (v<0)将具有值1 。 因此,当v为正((v>0) - (v<0)) = (1-0) = 1 。 整个expression式是: v * (1) == v

如果v是负数,则expression式(v>0)为假,并且在(v<0)为真(值1)时将具有值0 。 因此,对于负v((v>0) - (v<0)) = (0-1) = -1 。 整个expression式是: v * (-1) == -v

v == 0(v<0)(v>0)都将评估为0,并保留: v * 0 == 0

假设32位有符号整数(Java),你可以写:

 public static int abs(int x) { return (x + (x >> 31)) ^ (x >> 31); } 

没有乘法,没有分支。

顺便说一句, return (x ^ (x >> 31)) - (x >> 31); 将工作,但它是专利。 对!

注意:此代码可能比条件语句(8位Verison)长10倍以上。 这可能对硬件编程系统C等有用

以您考虑的方式移位有符号整数是未定义的行为,因此不是一个选项。 相反,你可以这样做:

 int abs(int n) { return n > 0 ? n : -n; } 

没有if语句,只是一个条件expression式。

尝试以下操作:

 int abs(int n) { return sqrt(n*n); } 

这里是另一种没有abs() ,如果没有任何逻辑/条件expression式:假设int在这里是32位整数。 这个想法很简单: (1 - 2 * sign_bit)sign_bit = 1 / 0 to -1 / 1 (1 - 2 * sign_bit)转换sign_bit = 1 / 0 to -1 / 1

 unsigned int abs_by_pure_math( int a ) { return (1 - (((a >> 31) & 0x1) << 1)) * a; } 

没有看到这一个。 对于二进制补码表示和32位整数

 ( n >> 31 | 1 ) * n 

我在C中尝试这个代码,它的工作原理。

 int abs(int n){ return n*((2*n+1)%2); } 

希望这个答案会有帮助。

使用三元运算符:

 y = condition ? value_if_true : value_if_false; 

那个怎么样:

 value = value > 0 ? value: ~value + 1 

它基于负数被存储为正相当于2的补码的事实,并且可以通过首先构build1的补码和加1来构build2的补码。

  5 -> 0000 0101b -5 -> (1111 1010b) + 1 -> 1111 1011b 

我所做的基本上是扭转这一点,所以

 -5 -> 1111 1011b 5 -> (0000 0100b) + 1 -> 0000 0101b 

我知道这有点晚了,但只是有同样的问题,降落在这里,希望这有助于。

还有一种方法可以做到这一点:

 int abs(int n) { int mask = n >> 31; return (mask & -n) | (~mask & n); } 

使用(mask & A) | (~mask & B) (mask & A) | (~mask & B)模式。 不知道它是否有名字。 我把它称为一个按分支。

如果你的语言允许布尔int转换(C / C ++的):

 float absB(float n) { return n - n * 2 * (n<0); } 

有多种原因左移标志位,右移回原位( v << 1 >> 1 ):

  • 左移一个带负值的带符号的types具有未定义的行为,所以根本不应该使用它。
  • 将值转换为unsigned值将会产生所需的效果:( (unsigned)v << 1 >> 1如果没有填充位, (unsigned)v << 1 >> 1符号位将消除符号位,但是结果值仅是v符号+幅度表示,这在当今是非常罕见的。 在无处不在的2的补码结构中,负v的结果值是INT_MAX+1-v

Hasturkun的解决scheme不幸的是有实现定义的行为。

下面是一个完全定义的系统,带有2的补码表示符号值的系统:

 int v; // we want to find the absolute value of v unsigned int r; // the result goes here unsigned int mask = -((unsigned int)v >> (sizeof(unsigned int) * CHAR_BIT - 1)); r = ((unsigned int)v + mask) ^ mask; 

你不得不按比例组合,而不是添加。

什么问题只是:

 -1 * n 

使用负号加上原则

如果你想要一个纯粹的math方法,不是昂贵,尝试

 f(x) = (x*x)/x 

或者在C ++中

 function abs(auto x) {return ((x*x)/x);}