【问题标题】:Computing integer absolute differences in overflow-safe ways?以溢出安全的方式计算整数绝对差?
【发布时间】:2011-12-22 21:27:36
【问题描述】:

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

  • 对于无符号整数,如果b 大于aa - b 将是一个很大的正数,而绝对值运算无法解决这个问题。

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

如何有效地解决这些问题?

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

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

【问题讨论】:

    标签: algorithm integer overflow


    【解决方案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);
    }
    

    正如 Brooks Moses 解释的那样,这种环绕是安全的。

    【讨论】:

    • 对,但是当a 是 INT_MAX 而b 是 INT_MIN 时,这如何解决有符号整数溢出的可能性?
    • 这不是绝对差异,对于有符号整数仍然容易溢出。
    • 好吧,我误解了这个问题。在这种情况下,我们希望始终返回无符号类型?
    • @JackToole:是的,让我试着澄清一下这个问题。没错,我们需要始终返回无符号类型。
    【解决方案2】:

    代码:

    #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
    

    【讨论】:

    • 您能解释一下签名函数中的diff = 1 + ~diff 行在做什么吗?这似乎是关键。
    • 取反一个 2 的补码整数的所有位,然后加 1 否定该数字,将正数变为负数,反之亦然。示例(使用 16 位无符号整数):1+~0=1+0xFFFF=0, 1+~1=1+0xFFFE=0xFFFF(=-1), 1+~0xFFFF(=-1)=1+0 =1, 1+~0x8000(=-0x8000)=1+0x7FFF=0x8000(=-0x8000)。基本上,我的函数首先执行数字的 2 补码减法(就像 x86 的 SUB 一样)。差异也是 2 的补码(但它可能缺少一个额外的符号位,例如当您在 AbsDiffSigned() 中得到它等于 0x80...00 时)。您从 (a
    • 好像没有人认真使用非2的补码..还不如使用一元减号
    • @harold:我使用1+~ 而不是- 的原因是一些编译器不喜欢在无符号值前面看到一元减号。我记得,警告级别设置为 4 的 MSVC++ 会这样做。另外,我不太确定这里是否有任何未定义的行为,虽然数学上 -X 应该等同于 0-X,但 C 的算术不是你的正常算术......
    • @harold:2003 年的 C++ 标准在 5.3.1c7 中说:The negative of an unsigned quantity is computed by subtracting its value from 2^n, where n is the number of bits in the promoted operand. 1999 年的 C 标准没有包含这样一个明确的声明,也没有明确定义一元 - 6.5.3.3 中的行为c1,3 也不是 6.5c4 (Some operators (the unary operator ~, and the binary operators &lt;&lt;, &gt;&gt;, &amp;, ^, and |, ...) ... return values that depend on the internal representations of integers, and have implementation-defined and undefined aspects for signed types.)。
    【解决方案3】:

    这里有个想法:如果我们没有签名,我们只取正确的差值。如果我们有签名,我们返回对应的无符号类型:

    #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);

    【讨论】:

    • +1,我一直在寻找一种使用模板使事物无符号的方法,但不幸的是还不能升级到 C++11。通过减去 std::numeric_limits::min() 并转换为 UT,可能有一种方法可以在没有 SFINAE 的情况下做到这一点。
    • @JackToole:不要放弃相信自己!仅仅因为make_signed 只是新标准的一部分,并不意味着您不能自己编写类似的东西。 (或者作弊并拿走boost::make_signed。):-)
    • 学究式地,签名案例中的第一个 if 需要检查 a&gt;=0 &amp;&amp; b&gt;=0 || a&lt;0 &amp;&amp; b&lt;0 以将零与正数集中在一起——否则,如果一个是 @987654327,您可能会溢出@ 而另一个是 0,因为 0 - INT_MININT_MAX + 1。 (这东西很棘手!)否则,它看起来对我来说是正确的——尽管它计算的每个值确实需要至少三个条件和两个分支,这似乎有点低效。
    • @BrooksMoses:我完全有可能错过了 INT_MIN 特殊情况,所以请随时添加。处理带符号的数字 很棘手,所以如果您有任何消除冗余的想法,请说出来。我只是当场编造的,目的是最大的问题是差异对于签名类型来说太大了。
    • @KerrekSB 是的:)。我作弊,找到了一个实现,但写起来并不难。只是很难想到。模板很棒,可以做各种巧妙的技巧,但我仍然需要更多的练习才能知道如何做高级技巧。
    【解决方案4】:

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

    无符号整数的解决方案相对简单(如 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 的实际值必须在0UINT_MAX-1 之间,所以我们知道这是完全相等的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-10-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-16
      • 2016-05-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多