这是怎么工作的?
这个想法是让abs(x)
对于整数(假设为32位字)使用位运算符:
y = x >> 31
(x + y) ^ y // This gives abs(x) (is ^ XOR)?
这是怎么工作的?
这个想法是让abs(x)
对于整数(假设为32位字)使用位运算符:
y = x >> 31
(x + y) ^ y // This gives abs(x) (is ^ XOR)?
假设32位字长,根据问题所述:
对于负数x
,在C和C ++标准中,x >> 31
是实现定义的。代码作者期望使用二进制补码整数和算术右移,在这种情况下,如果x
的符号位为零,则x >> 31
产生所有零位,如果符号位为1,则产生所有1位。
因此,如果x
是正数或零,则y
为零,x + y
即为x
,所以(x + y) ^ y
等于x
,即x
的绝对值。
x
是负数,y
的所有位都是1,代表二进制补码的-1。那么x+y
等于x-1
。然后与全1异或将反转所有位。反转所有位相当于取二进制补码再减1,而二进制补码是用于在二进制补码格式中表示负整数的方法。换句话说,与全1异或的结果为-q-1
。因此,x-1
与全1异或的结果为-(x-1)-1
=-x+1-1
=-x
,这是x
的绝对值(除非x
是该格式的最小可能值(32位二进制补码的-2,147,483,648),此时绝对值(2,147,483,648)太大无法表示,结果的位模式就是原始的x
)。-(2^(N-1) - 1)
到 +(2^(N-1) - 1)
之间,即一补数。大多数编译器都使用二补数,因为它们不是怪物,但这是可能的。C++20 标准要求整数为二补数。如Eric所述,对负整数进行右移是实现定义的,但可能是算术右移。 - JohnFilleauunsigned absx = x<0 ? -x : x;
(或者更好的避免负数溢出未定义行为的方法),它们会在适当的时候使用它。 - Peter Cordesx
的位数为32位。但是,您可以通过x >> (sizeof(x) * CHAR_BIT - 1)
来修复这个问题。使用3位进行示例:
101 -> x = -3
111 -> x >> 2
101 + 111 = 100 -> x + y
100 XOR 111 -> 011 -> 3
这不是可携带的。
int
为32位,因为问题中根本没有提到int
。代码明确表示它正在处理的单词,无论它们是什么类型,都是32位的。 - Eric Postpischil这不是可移植的,但我会解释为什么它能工作。
第一个操作利用了2的补码负数的特性,即第一位如果是1则表示为负数,如果是0则表示为正数。这是因为这些数字的范围从
下面的示例是针对8位的,但可以推广到任意位数。在你的情况下,是32位(但8位更容易显示范围)
10000000 (smallest negative number)
10000001 (next to smallest)
...
11111111 (negative one)
00000000 (zero)
00000001 (one)
...
01111110 (next to largest)
01111111 (largest)
y = x >> 31 // this line acts like an "if" statement.
// Depending on if y is 32 signed or unsigned, when x is negative,
// it will fill y with 0xFFFFFFFF or 1. The rest of the
// algorithm doesn't, care because it accommodates both inputs.
// when x is positive, the result is zero.
我们将探讨(首先是x为正数)
(x + y) ^ y // for positive x, first we substitute the y = 0
(x + 0) ^ 0 // reduce the addition
(x) ^ 0 // remove the parenthesis
x ^ 0 // which, by definition of xor, can only yield x
x
(x + y) ^ y // first substitute the Y
(x + 0xFFFFFFFF) ^ 0xFFFFFFFF // note that 0xFFFFF is the same as 2's complement -1
(x - 1) ^ 0xFFFFFFFF // add in a new variable Z to hold the result
(x - 1) ^ 0xFFFFFFFF = Z // take the ^ 0xFFFFFFFF of both sides
(x - 1) ^ 0xFFFFFFFF ^ 0xFFFFFFFF = Z ^ 0xFFFFFFFF // reduce the left side
(x - 1) = z ^ 0xFFFFFFFF // note that not is equivalent to ^ 0xFFFFFFFF
(x - 1) = ~(z) // add one to both sides
x - 1 + 1 = ~(z) + 1 // reduce
x = ~(z) + 1 // by definition z is negative x (for 2's complement numbers)
(x + y) ^ y // first substitute the Y
(x + 1) ^ 0x00000001 // note that x is a 2's complement negative, but is
// being treated as unsigned, so to make the unsigned
// context of x tracable, I'll add a -(x) around the X
(-(x) + 1) ^ 0x00000001 // which simplifies to
(-(x - 1)) ^ 0x00000001 // negative of a negative is positive
(-(x - 1)) ^ -(-(0x00000001)) // substituting 1 for bits of -1
(-(x - 1)) ^ -(0xFFFFFFFF) // pulling out the negative sign
-((x-1) ^ 0xFFFFFFFF) // recalling that while we added signs and negations to
// make the math sensible, there's actually no place to
// store them in an unsigned storage system, so dropping
// them is acceptable
x-1 ^ 0XFFFFFFFF = Z // introducing a new variable Z, take the ^ 0xFFFFFFF of both sides
x-1 ^ 0xFFFFFFFF ^ 0xFFFFFFFF = Z ^ 0xFFFFFFFF // reduce the left side
x-1 = z ^ 0xFFFFFFFF // note that not is equivalent to ^ 0xFFFFFFFF
x-1 = ~(z) // add one to both sides
x - 1 + 1 = ~(z) + 1 // reduce
x = ~(z) + 1 // by definition z is negative x (for 2's complement numbers, even though we used only non-2's complement types)
x = 0xFFFFFFFF
是 -1。我认为你指的是 x = 0x80000000
,这是二进制补码中最小的负数特殊情况。你可以通过将结果处理为无符号值来解决这个问题。(并且要小心编写代码,避免 C 语言中有符号溢出 UB 的可能性,例如通过使用无符号减法避免从 0x7FFFFFFF 溢出到 0x80000000) - Peter Cordes!
来进行位求反运算(按位取反)。但这是一个 C 语言问题;在 C 中,该运算符应该是 ~
(波浪号)。在 C 中,!
表示逻辑非,只能产生 0 或 1。 - Peter Cordes我使用这段代码,首先计算二进制补码(guard 仅通过编译时检查确保模板为整数)。
/**
* Zweierkomplement - Two's Complement
*/
template<typename T> constexpr auto ZQ(T const& _x) noexcept ->T{
Compile::Guards::IsInteger<T>();
return ((~(_x))+1);
}
其次,使用这个值计算整数 abs() 函数。
/**
* if number is negative, get the same number with positiv sign
*/
template<typename T> auto INTABS(T const _x) -> typename std::make_unsigned<T>::type{
Compile::Guards::IsInteger<T>();
return static_cast<typename std::make_unsigned<T>::type>((_x<0)?(ZQ<T>(_x)):(_x));
}
a >= 0 ? a : -a
更快的方法,请告诉编译器的开发人员,他们会改变它的优化。 - cmaster - reinstate monica