if
如何在不使用条件的情况下计算整数绝对值。我想我们需要使用一些按位运算。有人可以帮忙吗?
11 回答
与现有答案相同,但有更多解释:
让我们假设一个二进制补码数(因为这是通常的情况,你不会说别的),让我们假设 32 位:
首先,我们执行算术右移 31 位。这将所有1
s 转换为负数或所有0
s 转换为正数(但请注意,>>
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
使用补码表示时。
将掩码设置为整数右移 31(假设整数存储为二进制补码 32 位值并且右移运算符进行符号扩展)。
mask = n>>31
将掩码与数字异或
mask ^ n
从步骤 2 的结果中减去掩码并返回结果。
(mask^n) - mask
假设int
是 32 位的。
int my_abs(int x)
{
int y = (x >> 31);
return (x ^ y) - y;
}
也可以将上述操作执行为:
return n*(((n>0)<<1)-1);
其中n
是绝对需要计算的数字。
在发现这个问题之前,我自己写了。
我的回答可能较慢,但仍然有效:
int abs_of_x = ((x*(x >> 31)) | ((~x + 1) * ((~x + 1) >> 31)));
在 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 个字节。
如果您不允许使用减号,您可以执行以下操作:
int absVal(int x) {
return ((x >> 31) + x) ^ (x >> 31);
}
对于组装,最有效的方法是将值初始化为 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
在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正式合并了一个强大的内存模型,该模型严格保证了正确的获取顺序。我提到这个的原因是因为在或d
C
C++
人们可能需要更加谨慎。后者的内存模型更加宽松,这可能允许某些编译器优化发出乱序提取。显然,在这种情况下,获取顺序敏感性将代表正确性风险。
如果您不想在右位移位时依赖符号扩展的实现,您可以修改计算的方式mask
:
mask = ~((n >> 31) & 1) + 1
然后按照前面的答案中已经证明的那样进行:
(n ^ mask) - mask
您使用的编程语言是什么?在 C# 中,您可以使用以下Math.Abs
方法:
int value1 = -1000;
int value2 = 20;
int abs1 = Math.Abs(value1);
int abs2 = Math.Abs(value2);