减去/添加值而不会出现溢出或下溢

Ole*_*leg 82 c c++ optimization bit-manipulation saturation-arithmetic

想象一下,我有两个无符号字节bx.我需要计算bsubas b - xbaddas b + x.但是,我不希望在这些操作期间发生下溢/溢出.例如(伪代码):

b = 3; x = 5;
bsub = b - x; // bsub must be 0, not 254
Run Code Online (Sandbox Code Playgroud)

b = 250; x = 10;
badd = b + x; // badd must be 255, not 4
Run Code Online (Sandbox Code Playgroud)

显而易见的方法包括分支:

bsub = b - min(b, x);
badd = b + min(255 - b, x);
Run Code Online (Sandbox Code Playgroud)

我只是想知道是否有更好的方法来做到这一点,即通过一些hacky位操作?

Sha*_*our 85

文章Branchfree Saturating Arithmetic提供了以下策略:

他们的补充解决方案如下:

u32b sat_addu32b(u32b x, u32b y)
{
    u32b res = x + y;
    res |= -(res < x);

    return res;
}
Run Code Online (Sandbox Code Playgroud)

修改为uint8_t:

uint8_t  sat_addu8b(uint8_t x, uint8_t y)
{
    uint8_t res = x + y;
    res |= -(res < x);

    return res;
}
Run Code Online (Sandbox Code Playgroud)

他们的减法解决方案是:

u32b sat_subu32b(u32b x, u32b y)
{
    u32b res = x - y;
    res &= -(res <= x);

    return res;
}
Run Code Online (Sandbox Code Playgroud)

修改为uint8_t:

uint8_t sat_subu8b(uint8_t x, uint8_t y)
{
    uint8_t res = x - y;
    res &= -(res <= x);

    return res;
}
Run Code Online (Sandbox Code Playgroud)

  • @Yakk,啊,好的.我只是将此视为OP可以根据需要进行调整的最小示例.我不希望看到完整的实现.谢谢你的澄清. (6认同)
  • @Yakk是什么让这个"坏"的C++答案?这些是基本的数学运算,我不知道它将如何被解释为仅C或坏C++. (4认同)
  • @ JPhi1618一个更好的C++答案可能是`template <class T> struct sat {T t;};`重载运算符饱和了吗?正确使用命名空间.主要是糖. (4认同)
  • @ user1969104可能是这种情况,但正如文章中的注释所示,这是通过在应用一元减号之前转换为无符号来解决的.实际上[除了两个补充之外你不太可能需要处理其他任何事情](http://stackoverflow.com/q/12276957/1708801). (2认同)
  • 这可能是一个很好的C答案,但不是一个非常好的C++答案. (2认同)

use*_*104 40

一种简单的方法是检测溢出并相应地重置该值,如下所示

bsub = b - x;
if (bsub > b)
{
    bsub = 0;
}

badd = b + x;
if (badd < b)
{
    badd = 255;
}
Run Code Online (Sandbox Code Playgroud)

当使用-O2进行编译时,GCC可以将溢出检查优化为条件赋值.

我测量了与其他解决方案相比的优化程度.在我的PC上运行了100万次以上,这个解决方案和@ShafikYaghmour的平均时间为4.2秒,而@chux的平均时间为4.8秒.此解决方案也更具可读性.

  • @ user694733它没有被优化掉,它根据进位标志被优化为条件赋值. (5认同)
  • 是,user694733是正确的。它已优化为条件分配。 (2认同)

chu*_*ica 16

对于减法:

diff = (a - b)*(a >= b);
Run Code Online (Sandbox Code Playgroud)

加成:

sum = (a + b) | -(a > (255 - b))
Run Code Online (Sandbox Code Playgroud)

演化

// sum = (a + b)*(a <= (255-b)); this fails
// sum = (a + b) | -(a <= (255 - b)) falis too
Run Code Online (Sandbox Code Playgroud)

感谢@R_Kapp

感谢@NathanOliver

本练习显示了简单编码的价值.

sum = b + min(255 - b, a);
Run Code Online (Sandbox Code Playgroud)


小智 12

如果您使用的是最新版本的gcc或clang(也许还有其他版本),您可以使用内置函数来检测溢出.

if (__builtin_add_overflow(a,b,&c))
{
  c = UINT_MAX;
}
Run Code Online (Sandbox Code Playgroud)

  • 我不能让gcc用这个生成无懈可击的代码,这有点令人失望.这里特别不幸的是[clang使用不同的名称](http://stackoverflow.com/a/32317442/1708801). (3认同)