INT_MIN的绝对值的正确方法是什么?

20

我想在无符号数中进行一些算术运算,需要取负整数的绝对值,类似于

do_some_arithmetic_in_unsigned_mode(int some_signed_value)
{
   unsigned int magnitude;
   int negative;
   if(some_signed_value<0) {
       magnitude = 0 - some_signed_value;
       negative = 1;
    } else {
       magnitude = some_signed_value;
       negative = 0;
    }
   ...snip...
}

但是使用INT_MIN可能会存在问题,如果在有符号算术中执行0-INT_MIN操作就会产生UB(未定义行为)。那么,在C语言中有什么标准/稳健/安全/高效的方法来解决这个问题呢?

编辑:

如果我们知道我们处于2的补码环境中,也许隐式转换和显式位运算是标准的方式?如果可能的话,我想避免这种假设。

do_some_arithmetic_in_unsigned_mode(int some_signed_value)
{
   unsigned int magnitude=some_signed_value;
   int negative=some_signed_value<0;
   if (negative) {
       magnitude = (~magnitude) + 1;
    }
   ...snip...
}
5个回答

30

将有符号数转换为无符号数是明确定义的:您会得到相应的 2N 取模后的代表值。 因此,以下代码将为您提供正确的 n 的绝对值:

int n = /* ... */;

unsigned int abs_n = n < 0 ? UINT_MAX - ((unsigned int)(n)) + 1U
                           : (unsigned int)(n);

更新: 如@aka.nice所建议的那样,我们实际上可以用0U来替换UINT_MAX + 1U:


unsigned int abs_n = n < 0 ? -((unsigned int)(n))
                           : +((unsigned int)(n));

5
在纯理论情况下,假设 UINT_MAX == INT_MAX == -(INT_MIN+1),那么无论如何都无法用无符号整数来表示 |INT_MIN| =) - Daniel Fischer
1
可能会出现unsigned intint多一个填充位的情况。我从未听说过这种情况的实现,但标准并不保证它永远不会发生。(除非我漏看了什么。) - Daniel Fischer
1
如果我写abs_n = n<0 ? 0U - ((unsigned int)(n)) : ((unsigned int)(n));,它的定义是否同样良好? - aka.nice
1
实际上,我的提议的测试是错误的。它考虑了2的补码实现,但没有考虑1的补码或符号-幅度,其中(unsigned)INT_MIN != 0即使绝对值不适合也是正确的。如果您希望函数返回unsigned int,则“Fischer条件”是必要且充分的,但是正确的测试并不那么简单... - Steve Jessop
@DanielFischer 关于 one more padding bit,在 C99 之前,我使用了这种方法,其中 long/unsigned long 是 64 位的,除了 unsigned long 有一个填充位。似乎处理器没有 64 位 unsigned 原生的 *,/ 指令,所以他们采取了简单的方式。 - chux - Reinstate Monica
显示剩余4条评论

6
在负数情况下,取some_signed_value+1。对其取反(这是安全的,因为它不能是INT_MIN)。转换为无符号数。然后加一。

我检查了一下,gcc为1U +(unsigned)(-(x + 1))和-(unsigned)(x)生成相同的代码,类似于(~ magnitude)+1但是无跳转,因此两者都同样有效。不过后一个看起来没有那么难以理解意图。 - aka.nice
@aka.nice:是的,我也刚刚检查了一下。1 + (unsigned)-(x + 1)可能有点晦涩,但它不会将负有符号数值转换为无符号数值;强制类型转换仅仅是类型的变化,而不是值的变化。在你的版本中,需要进行一些推理工作来确保算术运算符执行预期的操作;这个论点并不像“每个步骤中的值都在安全范围内”那么简单。 - R.. GitHub STOP HELPING ICE
1
是的,确实你的解决方案更符合我的初衷。重新将负x解释为正数会使代码的含义变得模糊,需要对标准约定有一定的了解。但是不太注意细节的读者会立即认出-(unsigned)x中的abs形式。 - aka.nice
建议将以下代码添加到这个好的答案中:unsigned abs_u(int i) { return i < 0 ? -(i+1) + 1u : (unsigned) i; } - chux - Reinstate Monica

2

您始终可以测试>= -INT_MAX,这总是有明确定义的。唯一有趣的情况是如果INT_MIN < -INT_MAX并且some_signed_value == INT_MIN。您必须单独测试该情况。


2
我想在无符号数中进行一些算术运算,并需要取负整数的绝对值,...
为了处理严格的情况: |SOME_INT_MIN|1 有一些特殊情况:
1. 非二进制补码
反码和补码很少见。
SOME_INT_MIN == -SOME_INT_MAX 并且 some_abs(some_int) 已经被定义好了。这是容易处理的情况。
#if INT_MIN == -INT_MAX
some_abs(x);  // use matching abs, labs, llabs, imaxabs
#endif

2. SOME_INT_MAX == SOME_UINT_MAX, 2的补码

C语言允许有符号和无符号整数类型的最大值相同。但这种情况在现今很少出现。

有两种解决方法:
1)如果存在更宽的整数类型,使用该类型。

#if -INTMAX_MAX <= SOME_INT_MIN
imaxabs((intmax_t)x)
#endif

2) 使用更宽的浮点数类型。
将整数转换为更宽的浮点数类型对于SOME_INT_MIN(二进制补码)可以工作,因为该值是-(2的幂次方)。 对于其他大的负数,强制转换可能会导致宽整数失去精度,而不是宽long double。例如,64位long long和64位long double

fabsl(x);  // see restriction above.

3. SOME_INT_MAX < SOME_UINT_MAX

这是一个常见的情况,可以通过@Kerrek SB的答案很好地解决。 下面的内容也会处理第一种情况。

x < 0 ? -((unsigned) x) : ((unsigned) x);

更高级的替代方法

在代码中出现 .... + abs(x) 时,可以使用一个明确定义的替代方法:减去负的绝对值:.... - nabs(x)。或者在 abs(x) < 100 的情况下,使用 nabs > -100

// This is always well defined.
int nabs(int x) {
  return (x < 0) x : -x;
}

1 SOME_INT 意味着 intlonglong longintmax_t


0
 static unsigned absolute(int x)
 {
          if (INT_MIN == x) {
                  /* Avoid tricky arithmetic overflow possibilities */
                  return ((unsigned) -(INT_MIN + 1)) + 1U;
          } else if (x < 0) {
                  return -x;
          } else {
                  return x;
          }
 }

听起来还不错,但主要是@R的答案再加上一个分支,或者只是@Jens的答案... - aka.nice

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接