33

if如何在不使用条件的情况下计算整数绝对值。我想我们需要使用一些按位运算。有人可以帮忙吗?

4

11 回答 11

91

与现有答案相同,但有更多解释:

让我们假设一个二进制补码数(因为这是通常的情况,你不会说别的),让我们假设 32 位:

首先,我们执行算术右移 31 位。这将所有1s 转换为负数或所有0s 转换为正数(但请注意,>>C 或 C++ 中的实际 - 运算符行为是为负数定义的实现,但通常也会执行算术移位,但让我们假设伪代码或实际的硬件说明,因为无论如何这听起来像是家庭作业):

mask = x >> 31;

所以我们得到的是111...111(-1)负数和000...000(0)正数

现在我们将它与 进行异或x,得到 NOT for mask=111...111(negative) 和 no-op for mask=000...000(positive) 的行为:

x = x XOR mask;

最后减去我们的掩码,这意味着 +1 表示负数,+0/no-op 表示正数:

x = x - mask;

因此,对于正数,我们执行 0 和减法 0 的异或运算,从而得到相同的数字。对于负数,我们得到(NOT x) + 1,这正是-x使用补码表示时。

于 2012-08-20T17:00:31.323 回答
33
  1. 将掩码设置为整数右移 31(假设整数存储为二进制补码 32 位值并且右移运算符进行符号扩展)。

    mask = n>>31 
    
  2. 将掩码与数字异或

    mask ^ n 
    
  3. 从步骤 2 的结果中减去掩码并返回结果。

    (mask^n) - mask 
    
于 2012-08-20T16:45:32.080 回答
6

假设int是 32 位的。

int my_abs(int x)
{
    int y = (x >> 31);
    return (x ^ y) - y;
}
于 2012-08-20T16:46:37.147 回答
2

也可以将上述操作执行为:

return n*(((n>0)<<1)-1);

其中n是绝对需要计算的数字。

于 2015-11-22T11:19:33.463 回答
1

在发现这个问题之前,我自己写了。

我的回答可能较慢,但仍然有效:

int abs_of_x = ((x*(x >> 31)) | ((~x + 1) * ((~x + 1) >> 31)));
于 2014-09-04T15:09:41.477 回答
1

在 C 中,您可以使用联合对双精度数执行位操作。以下将在 C 中工作,可用于整数、浮点数和双精度数。

/**
* Calculates the absolute value of a double.
* @param x An 8-byte floating-point double
* @return A positive double
* @note Uses bit manipulation and does not care about NaNs
*/
double abs(double x)
{
    union{
        uint64_t bits;
        double dub;
    } b;

    b.dub = x;

    //Sets the sign bit to 0
    b.bits &= 0x7FFFFFFFFFFFFFFF;

    return b.dub;
}

请注意,这假定双精度为 8 个字节。

于 2016-11-07T17:42:53.077 回答
1

如果您不允许使用减号,您可以执行以下操作:

int absVal(int x) {
  return ((x >> 31) + x) ^ (x >> 31);
}
于 2018-09-22T19:11:34.640 回答
0

对于组装,最有效的方法是将值初始化为 0,减去整数,然后取最大值:

pxor mm1, mm1 ; set mm1 to all zeros
psubw mm1, mm0 ; make each mm1 word contain the negative of each mm0 word
pmaxswmm1, mm0 ; mm1 will contain only the positive (larger) values - the absolute value
于 2018-01-25T16:57:41.960 回答
0

C#中,您可以在abs()不使用任何局部变量的情况下实现:

public static long abs(long d) => (d + (d >>= 63)) ^ d;

public static int abs(int d) => (d + (d >>= 31)) ^ d;

注意:关于 0x80000000 (int.MinValue) 0x8000000000000000 (long.MinValue)

与此页面上显示的所有其他按位/非分支方法一样,这给出了单个非数学结果abs(int.MinValue) == int.MinValue(同样适用于long.MinValue)。这些表示结果值为负数的唯一情况,即二进制补码结果的MSB为-- 并且也是唯一返回输入值不变的情况。我不相信这个重要的一点在这个页面的其他地方提到过。1

上面显示的代码取决于xor右侧d使用的值,即左侧计算期间更新的值。对于C#程序员来说,这似乎很明显。他们习惯于看到这样的代码,因为.NET正式合并了一个强大的内存模型,该模型严格保证了正确的获取顺序。我提到这个的原因是因为在或dCC++人们可能需要更加谨慎。后者的内存模型更加宽松,这可能允许某些编译器优化发出乱序提取。显然,在这种情况下,获取顺序敏感性将代表正确性风险。

于 2018-05-12T08:43:18.900 回答
0

如果您不想在右位移位时依赖符号扩展的实现,您可以修改计算的方式mask

mask = ~((n >> 31) & 1) + 1

然后按照前面的答案中已经证明的那样进行:

(n ^ mask) - mask
于 2020-11-04T00:27:05.633 回答
-1

您使用的编程语言是什么?在 C# 中,您可以使用以下Math.Abs方法:

int value1 = -1000;
int value2 = 20;
int abs1 = Math.Abs(value1);
int abs2 = Math.Abs(value2);
于 2012-08-20T16:44:50.023 回答