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

Algorithm 以溢出安全方式计算整数绝对差?,algorithm,integer,overflow,Algorithm,Integer,Overflow,我想计算两个整数的绝对差。天真地说,这就是abs(a-b)。但是,这有几个问题,这取决于整数是有符号的还是无符号的: 对于无符号整数,如果b大于a,则a-b将是一个大的正数,绝对值操作不会解决这个问题 对于有符号整数,a-b可能超出可以表示为有符号整数的值范围,从而导致未定义的行为。(显然,这意味着结果必须是无符号整数。) 如何有效地解决这些问题 我想要一个算法(或一对算法),既适用于有符号整数又适用于无符号整数,并且不依赖于将值强制转换为更大的整数大小 (另外,要澄清的是:我的问题不是如何

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

  • 对于无符号整数,如果
    b
    大于
    a
    ,则
    a-b
    将是一个大的正数,绝对值操作不会解决这个问题

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

如何有效地解决这些问题

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


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

编辑为使用布鲁克·摩西(Brook Moses)对签名类型的修复和克里·SB(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 struct make_unsigned{};
模板结构make_unsigned{};
模板结构make_unsigned{typedef unsigned short type;};
模板结构make_unsigned{typedef unsigned short type;};
模板结构make_unsigned{typedef unsigned int type;};
模板结构make_unsigned{typedef unsigned int type;};
模板结构make_unsigned{typedef unsigned long type;};
模板结构make_unsigned{typedef unsigned long type;};
模板结构make_unsigned{typedef unsigned long long type;};
模板结构make_unsigned{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);
}
模板
typename make_unsigned::type absdiff(ta,tb)
{
typedef typename make_unsigned::type UT;
如果(a>b)
返回静态_-cast(a)-静态_-cast(b);
其他的
返回静态强制转换(b)-静态强制转换(a);
}
正如布鲁克斯·摩西所解释的那样,这个概括是安全的。

(我在问了这个问题之后,一直在自己解决这个问题——我认为这会更难,如果有更好的答案,我仍然欢迎其他答案!)

无符号整数的解决方案相对简单(如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
(无符号)a
是模
UINT_MAX
全等的。此外,无符号整数减法运算与实际减法模
UINT_MAX
是一致的,因此结合这些事实,我们知道
(无符号)a-(无符号)b
a-b
UINT_MAX
的实际值是一致的。最后,因为我们知道
a-b
的实际值必须介于
0
UINT_MAX-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);
}
#包括
模板结构absdiff_aux;
模板结构absdiff_aux
{
静态T absdiff(ta,tb){返回a0&&b>0)| |(a代码:


是的,但这如何解决有符号整数溢出的可能性,比如说,
a
是INT\u MAX,
b
是INT\u MIN?这不是一个绝对的差异,仍然容易导致有符号整数溢出。好吧,我误解了这个问题。在这种情况下,我们希望总是返回一个无符号类型?@JackToole:是的,让我不要ry来澄清这个问题。你说得对,我们需要始终返回一个未签名的类型。+1,我在寻找一种方法来使用模板使内容未签名,但不幸的是,还不能升级到C++11。可能有一种方法可以在没有太多SFINAE的情况下实现这一点,方法是减去std::numeric_limits::min()和UT一起铸造。@JackToole:不要放弃相信自己!仅仅因为
make\u signed
只是新标准的一部分,并不意味着你不能自己写类似的东西。(或者欺骗并接受
boost::make\u signed
):-)学究般地说,签名案例中的第一个
if
需要检查
a>=0&&b>=0 ||a@BrooksMoses:我完全有可能错过了INT_MIN特例,所以请随意添加。处理有符号的数字很棘手,所以如果你有任何消除冗余的想法,一定要说。我只是在spo上编造了这个t、 针对差异对于有符号类型来说太大的最大问题。@KerrekSB-Yep:)。我作弊并找到了一个实现,但写起来并不难。只是想起来很难。模板很棒,可以做各种巧妙的技巧,但我仍然需要更多的练习来了解如何做高级技巧。你能解释一下签名函数中的
diff=1+~diff
行在做什么吗?这似乎是关键。在晕眩
#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