以溢出安全的方式计算整数绝对差异?

时间:2011-11-06 00:49:10

标签: algorithm integer overflow

我想计算两个整数的绝对差值。天真地,这只是abs(a - b)。但是,这有两个问题,具体取决于整数是有符号还是无符号:

  • 对于无符号整数,如果a - b大于ba将是一个大的正数,绝对值操作将无法解决此问题。

  • 对于有符号整数,a - b可能超出可以表示为有符号整数的值范围,从而导致未定义的行为。 (显然,这意味着结果必须是无符号整数。)

如何以有效的方式解决这些问题?

我想要一个适用于有符号和无符号整数的算法(或算法对),并且不依赖于将值转换为更大的整数。

(另外,澄清一下:我的问题不是如何在代码中写这个,而是我应该写什么才能保证溢出安全。将abs(a - b)计算为有符号值然后转换为无符号值不起作用,因为有符号整数溢出通常是未定义的操作,至少在C中。)

4 个答案:

答案 0 :(得分:7)

(我在提出这个问题之后一直在自己解决这个问题 - 我认为这会更难,如果有更好的答案,我还是会欢迎其他答案!)

无符号整数的解决方案相对简单(如Jack Toole的回答所述),并且通过在减法之外移动(隐含的)条件来工作,这样我们总是从较大的数字中减去较小的数字,而不是比较可能包装的值为零:

if (a > b)
  return a - b;
else
  return b - a;

这就留下了有符号整数的问题。请考虑以下变体:

if (a > b)
  return (unsigned) a - (unsigned) b;
else
  return (unsigned) b - (unsigned) a;

我们可以通过使用模运算来轻松证明这是有效的。我们知道a(unsigned) a是一致的模UINT_MAX。此外,无符号整数减法运算与实际减法模UINT_MAX一致,因此结合这些事实,我们知道(unsigned) a - (unsigned) ba - bUINT_MAX的实际值一致。a - b }。最后,因为我们知道0的实际值必须介于UINT_MAX-1和{{1}}之间,所以我们知道这是完全相等的。

答案 1 :(得分:1)

编辑使用Brook Moses对签名类型的修复和Kerrek SB的make_unsigned允许 模板使用。

首先,你可以为make_unsigned提供以下内容,或者你可以使用std :: make_unsigned,tr1 :: make_unsigned或BOOST :: make_unsigned。

template <typename T> struct make_unsigned { };

template <> struct make_unsigned<bool              > {};
template <> struct make_unsigned<  signed short    > {typedef unsigned short     type;};
template <> struct make_unsigned<unsigned short    > {typedef unsigned short     type;};
template <> struct make_unsigned<  signed int      > {typedef unsigned int       type;};
template <> struct make_unsigned<unsigned int      > {typedef unsigned int       type;};
template <> struct make_unsigned<  signed long     > {typedef unsigned long      type;};
template <> struct make_unsigned<unsigned long     > {typedef unsigned long      type;};
template <> struct make_unsigned<  signed long long> {typedef unsigned long long type;};
template <> struct make_unsigned<unsigned long long> {typedef unsigned long long type;};

然后,模板定义变得简单:

template <typename T>
typename make_unsigned<T>::type absdiff(T a, T b)
{
    typedef typename make_unsigned<T>::type UT;
    if (a > b)
        return static_cast<UT>(a) - static_cast<UT>(b);
    else
        return static_cast<UT>(b) - static_cast<UT>(a);
}

布鲁克斯摩西解释说,这种环绕是安全的。

答案 2 :(得分:1)

这是一个想法:如果我们没有签名,我们只是采取正确的区别。如果我们签名,我们将返回相应的无符号类型:

#include <type_traits>

template <typename T, bool> struct absdiff_aux;

template <typename T> struct absdiff_aux<T, true>
{
  static T absdiff(T a, T b)  { return a < b ? b - a : a - b; }
};

template <typename T> struct absdiff_aux<T, false>
{
  typedef typename std::make_unsigned<T>::type UT;
  static UT absdiff(T a, T b)
  {
    if ((a > 0 && b > 0) || (a <= 0 && b <= 0))
      return { a < b ? b - a : a - b; }

    if (b > 0)
    {
      UT d = -a;
      return UT(b) + d
    }
    else
    {
      UT d = -b;
      return UT(a) + d;
    }
  }
};

template <typename T> typename std::make_unsigned<T>::type absdiff(T a, T b)
{
  return absdiff_aux<T, std::is_signed<T>::value>::absdiff(a, b);
}

用法:int a, b; unsigned int d = absdiff(a, b);

答案 3 :(得分:1)

代码:

#include <stdio.h>
#include <limits.h>

unsigned AbsDiffSigned(int a, int b)
{
  unsigned diff = (unsigned)a - (unsigned)b;
  if (a < b) diff = 1 + ~diff;
  return diff;
}

unsigned AbsDiffUnsigned(unsigned a, unsigned b)
{
  unsigned diff = a - b;
  if (a < b) diff = 1 + ~diff;
  return diff;
}

int testDataSigned[] =
{
  { INT_MIN },
  { INT_MIN + 1 },
  { -1 },
  { 0 },
  { 1 },
  { INT_MAX - 1 },
  { INT_MAX }
};

unsigned testDataUnsigned[] =
{
  { 0 },
  { 1 },
  { UINT_MAX / 2 + 1 },
  { UINT_MAX - 1 },
  { UINT_MAX }
};

int main(void)
{
  int i, j;

  printf("Absolute difference of signed integers:\n");

  for (j = 0; j < sizeof(testDataSigned)/sizeof(testDataSigned[0]); j++)
    for (i = 0; i < sizeof(testDataSigned)/sizeof(testDataSigned[0]); i++)
      printf("abs(%d - %d) = %u\n",
             testDataSigned[j],
             testDataSigned[i],
             AbsDiffSigned(testDataSigned[j], testDataSigned[i]));

  printf("Absolute difference of unsigned integers:\n");

  for (j = 0; j < sizeof(testDataUnsigned)/sizeof(testDataUnsigned[0]); j++)
    for (i = 0; i < sizeof(testDataUnsigned)/sizeof(testDataUnsigned[0]); i++)
      printf("abs(%u - %u) = %u\n",
             testDataUnsigned[j],
             testDataUnsigned[i],
             AbsDiffUnsigned(testDataUnsigned[j], testDataUnsigned[i]));
  return 0;
}

输出:

Absolute difference of signed integers:
abs(-2147483648 - -2147483648) = 0
abs(-2147483648 - -2147483647) = 1
abs(-2147483648 - -1) = 2147483647
abs(-2147483648 - 0) = 2147483648
abs(-2147483648 - 1) = 2147483649
abs(-2147483648 - 2147483646) = 4294967294
abs(-2147483648 - 2147483647) = 4294967295
abs(-2147483647 - -2147483648) = 1
abs(-2147483647 - -2147483647) = 0
abs(-2147483647 - -1) = 2147483646
abs(-2147483647 - 0) = 2147483647
abs(-2147483647 - 1) = 2147483648
abs(-2147483647 - 2147483646) = 4294967293
abs(-2147483647 - 2147483647) = 4294967294
abs(-1 - -2147483648) = 2147483647
abs(-1 - -2147483647) = 2147483646
abs(-1 - -1) = 0
abs(-1 - 0) = 1
abs(-1 - 1) = 2
abs(-1 - 2147483646) = 2147483647
abs(-1 - 2147483647) = 2147483648
abs(0 - -2147483648) = 2147483648
abs(0 - -2147483647) = 2147483647
abs(0 - -1) = 1
abs(0 - 0) = 0
abs(0 - 1) = 1
abs(0 - 2147483646) = 2147483646
abs(0 - 2147483647) = 2147483647
abs(1 - -2147483648) = 2147483649
abs(1 - -2147483647) = 2147483648
abs(1 - -1) = 2
abs(1 - 0) = 1
abs(1 - 1) = 0
abs(1 - 2147483646) = 2147483645
abs(1 - 2147483647) = 2147483646
abs(2147483646 - -2147483648) = 4294967294
abs(2147483646 - -2147483647) = 4294967293
abs(2147483646 - -1) = 2147483647
abs(2147483646 - 0) = 2147483646
abs(2147483646 - 1) = 2147483645
abs(2147483646 - 2147483646) = 0
abs(2147483646 - 2147483647) = 1
abs(2147483647 - -2147483648) = 4294967295
abs(2147483647 - -2147483647) = 4294967294
abs(2147483647 - -1) = 2147483648
abs(2147483647 - 0) = 2147483647
abs(2147483647 - 1) = 2147483646
abs(2147483647 - 2147483646) = 1
abs(2147483647 - 2147483647) = 0
Absolute difference of unsigned integers:
abs(0 - 0) = 0
abs(0 - 1) = 1
abs(0 - 2147483648) = 2147483648
abs(0 - 4294967294) = 4294967294
abs(0 - 4294967295) = 4294967295
abs(1 - 0) = 1
abs(1 - 1) = 0
abs(1 - 2147483648) = 2147483647
abs(1 - 4294967294) = 4294967293
abs(1 - 4294967295) = 4294967294
abs(2147483648 - 0) = 2147483648
abs(2147483648 - 1) = 2147483647
abs(2147483648 - 2147483648) = 0
abs(2147483648 - 4294967294) = 2147483646
abs(2147483648 - 4294967295) = 2147483647
abs(4294967294 - 0) = 4294967294
abs(4294967294 - 1) = 4294967293
abs(4294967294 - 2147483648) = 2147483646
abs(4294967294 - 4294967294) = 0
abs(4294967294 - 4294967295) = 1
abs(4294967295 - 0) = 4294967295
abs(4294967295 - 1) = 4294967294
abs(4294967295 - 2147483648) = 2147483647
abs(4294967295 - 4294967294) = 1
abs(4294967295 - 4294967295) = 0