获取绝对值,而不使用 ABS 函数或 if 语句

get absolute value without using abs function nor if statement

本文关键字:函数 ABS if 语句 取绝对值      更新时间:2023-10-16

我在想如何在不使用语句或abs()的情况下获取整数if绝对值。起初我使用左移位(<<(,试图将负号移出范围,然后将位移回原位,但不幸的是它对我不起作用。请让我知道为什么它不起作用以及其他替代方法。

来自 bit twiddling hacks:

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 v) 
{
  return v * ((v>0) - (v<0));
}

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

如果v为正,则表达式 (v>0) 为真,并且值1,而(v<0)为假(值 0 表示假(。因此,当v为正时((v>0) - (v<0)) = (1-0) = 1.整个表达是:v * (1) == v.

如果v为负数,则表达式 (v>0) 为假,值将0,而(v<0)为真(值 1(。因此,对于负v((v>0) - (v<0)) = (0-1) = -1 。整个表达方式是:v * (-1) == -v.

v == 0时,(v<0)(v>0) 的计算结果都将为 0,留下:v * 0 == 0

Branchless:

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.

我在 C 中尝试了这段代码,它可以工作。

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

希望这个答案会有所帮助。

假设有

32 位有符号整数 (Java(,你可以这样写:

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

乘法,没有分支。

顺便说一句,return (x ^ (x >> 31)) - (x >> 31);也可以工作,但它已获得专利。是的!

注意:此代码可能需要比条件语句(8 位版本(长 10 倍以上的时间。这对于硬件编程系统C等可能很有用

尝试以下操作:

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

没看到这个。对于 2 的补码表示和 32 位 int

( n >> 31 | 1 ) * n

没有分支或乘法:

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

这是另一种没有abs()的方法,如果或任何逻辑/条件表达式:假设 int 在这里是 32 位整数。这个想法很简单:(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;
}

如果你的语言允许 bool 到 int cast(C/C++ like(:

float absB(float n) {
    return n - n * 2.0f * ( n < 0.0f );
}

以您认为的方式将有符号整数移位是未定义的行为,因此不是一种选择。相反,您可以这样做:

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

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

有多种原因导致左移标志位和右移回原位(v << 1 >> 1(:

  • 左移 具有负值的有符号类型具有未定义的行为,因此根本不应使用它。
  • 将值转换为unsigned将具有预期的效果:如果没有填充位,(unsigned)v << 1 >> 1确实会删除符号位,但结果值是仅在具有符号+幅度表示的系统上v的绝对值,这在当今已经非常罕见。 在无处不在 2 的补码架构上,负v的结果值为 INT_MAX+1-v

不幸的是,Hasturkun的解决方案具有实现定义的行为。

以下是为有符号值具有 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;

这个呢:

#include <climits>
long abs (int n) { // we use long to avoid issues with INT MIN value as there is no positive equivalents.
    const long ret[2] = {n, -n};
    return ret[n >> (sizeof(int) * CHAR_BIT - 1)];    // we use the most significant bit to get the right index.
}

位移(原则上(是实现定义的,但转换为更广泛的有符号整数类型将扩展符号位。如果您将 hi-bit 解释为整数,它们将是 0 或 -1,这将允许您反转 2 的补码:

int32_t abs(int32_t in)
{
  int64_t in64 = (int64_t)in;
  int32_t* ptr = (int32_t*)&in64;
  int32_t hi = *(++ptr); // assumes little-endian
  int32_t out = (in ^ hi) - hi;
  return out;
}

上述机制是在启用优化的情况下编译朴素实现的结果:

mov         eax,ecx  
cdq  
xor         eax,edx  
sub         eax,edx  

一个避免位移的简单方法:

value -= (value < 0) * value * 2;
  • 如果value为正数,则value < 0的计算结果为 0,因此乘法结果为 0 。没有什么是从value中减去的.
  • 如果value 0,乘法结果也是0
  • 如果value为负数,则value < 0true,其计算结果为 1 ,然后乘以 value * 2 。从本质上讲,你正在从价值中减去价值的两倍,从而获得相应的正值。
怎么样

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

它基于负数存储为 2 的补码到那里的正等价数的事实,并且可以通过首先构建 1 的补码并添加 1 来构建 2 的补码,因此

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

我所做的基本上就是为了扭转这种情况,所以

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

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

使用除法(和更广泛的数学(来形成一个"if"。 也许效率不高,但无分支。

int abs_via_division(int v) {
  // is_neg:0 when v >= 0
  //        1 when v < 0
  int is_neg = (int) ((4LL * v) / (4LL * v + 1));
  return  v * (1 - is_neg*2);
}

适用于long longint宽时的所有int,除了通常的|INT_MIN|麻烦。

使用三元运算符:

y = condition ? value_if_true : value_if_false;