C++ 对无符号字节进行饱和减/加

C++ 对无符号字节进行饱和减/加,c++,c,optimization,bit-manipulation,saturation-arithmetic,C++,C,Optimization,Bit Manipulation,Saturation Arithmetic,假设我有两个无符号字节b和x。我需要将bsub计算为b-x和badd计算为b+x。但是,我不希望在这些操作期间发生下溢/溢出。例如(伪代码): 及 显而易见的方法包括分支: bsub = b - min(b, x); badd = b + min(255 - b, x); 我只是想知道是否有更好的方法来实现这一点,即通过一些老套的位操作?对于减法: diff = (a - b)*(a >= b); unsigned temp = a-b; // temp>>8 will

假设我有两个无符号字节
b
x
。我需要将
bsub
计算为
b-x
badd
计算为
b+x
。但是,我不希望在这些操作期间发生下溢/溢出。例如(伪代码):

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

bsub = b - min(b, x);
badd = b + min(255 - b, x);
我只是想知道是否有更好的方法来实现这一点,即通过一些老套的位操作?

对于减法:

diff = (a - b)*(a >= b);
unsigned temp = a-b;  // temp>>8 will be 0xFF if neg-overflow else 0
unsigned char c = temp & ~(temp >> 8);
补充:

sum = (a + b) | -(a > (255 - b))

进化

// sum = (a + b)*(a <= (255-b)); this fails
// sum = (a + b) | -(a <= (255 - b)) falis too
那么这个呢:

bsum = a + b;
bsum = (bsum < a || bsum < b) ? 255 : bsum;

bsub = a - b;
bsub = (bsub > a || bsub > b) ? 0 : bsub;
bsum=a+b;
bsum=(bsuma | | bsub>b)?0:bsub;

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

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

badd = b + x;
if (badd < b)
{
    badd = 255;
}
bsub=b-x;
如果(b sub>b)
{
bsub=0;
}
badd=b+x;
如果(添加
在使用-O2编译时,GCC可以将溢出检查优化为条件赋值


我测量了与其他解决方案相比优化的程度。在我的电脑上进行100000000多次操作后,此解决方案和@ShafikYaghmour的平均时间为4.2秒,@chux的平均时间为4.8秒。此解决方案也更具可读性。

您还可以使用位于的安全数字库。它提供int、long等的插入式替换。。。这保证了您永远不会出现未检测到的溢出、下溢等情况。

本文提供了以下策略:

其添加溶液如下所示:

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

    return res;
}
u32b卫星加上u32b(u32b x,u32b y)
{
u32b res=x+y;
res |=-(res
针对uint8\u t进行了修改:

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

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

    return res;
}
uint8\u t sat\u addu8b(uint8\u t x,uint8\u t y)
{
uint8_t res=x+y;
res |=-(res
他们的减法解是:

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

    return res;
}
u32b卫星(u32b x,u32b y)
{
u32b res=x-y;

res&=-(res所有操作都可以在无符号字节算术中完成

// Addition without overflow
return (b > 255 - a) ? 255 : a + b

// Subtraction without underflow
return (b > a) ? 0 : a - b;

如果您经常调用这些方法,最快的方法不是位操作,而是查找表。为每个操作定义一个长度为511的数组。 减法(减法)示例

它是如何工作的?您想预先计算无符号字符的所有可能减法。结果从-255到+255不等,总共有511个不同的结果。我们定义了一个包含所有可能结果的数组,但因为在C中,我们无法从负索引访问它,所以我们使用+255(在[A-B+255]中)。您可以通过定义指向数组中心的指针来删除此操作

const unsigned char *result = maxTable+255;
#define MINUS(A,B)    result[A-B];
像这样使用它:

bsub  = MINUS(13,15); // i.e 13-15 with zero cutoff as requested
请注意,执行速度非常快。只需执行一次减法和一个指针即可获得结果。无分支。静态数组非常短,因此它们将完全加载到CPU缓存中,以进一步加快计算速度

同样的方法也适用于加法,但使用一个稍微不同的表(前256个元素将是索引,最后255个元素将等于255,以模拟255之后的截止值

如果您坚持bits操作,则使用(a>b)的答案是错误的。这仍然可能被实现为分支。请使用符号位技术

// (num1>num2) ? 1 : 0
#define        is_int_biggerNotEqual( num1,num2) ((((__int32)((num2)-(num1)))&0x80000000)>>31)
现在您可以使用它来计算减法和加法

如果要在不进行分支的情况下模拟函数max()、min(),请使用:

inline __int32 MIN_INT(__int32 x, __int32 y){   __int32 d=x-y; return y+(d&(d>>31)); }              

inline __int32 MAX_INT(__int32 x, __int32 y){   __int32 d=x-y; return x-(d&(d>>31)); }
我上面的示例使用32位整数。您可以将其更改为64位,但我认为32位计算的运行速度要快一点。添加操作由您决定:

unsigned temp = a+b;  // temp>>8 will be 1 if overflow else 0
unsigned char c = temp | -(temp >> 8);
减法:

diff = (a - b)*(a >= b);
unsigned temp = a-b;  // temp>>8 will be 0xFF if neg-overflow else 0
unsigned char c = temp & ~(temp >> 8);

不需要比较运算符或乘法器。

如果要使用两个字节执行此操作,请使用最简单的代码


如果要使用200亿字节执行此操作,请检查处理器上有哪些向量指令,以及它们是否可以使用。您可能会发现处理器可以使用一条指令执行其中32个操作。

如果您使用的是足够新的gcc或clang版本(可能还有其他版本)您可以使用来检测溢出

if (__builtin_add_overflow(a,b,&c))
{
  c = UINT_MAX;
}

如果您愿意使用assembly或Intrinsic,我想我有一个最佳解决方案

对于减法:

我们可以使用

在MSVC中,我们可以使用内部函数(也可以在其他位大小中使用)

下面是它的使用方法:

// *c = a - (b + borrow)
// borrow_flag is set to 1 if (a < (b + borrow))
borrow_flag = _subborrow_u64(borrow_flag, a, b, c);
// *c = a + b + carry
// carry_flag is set to 1 if there is a carry bit
carry_flag = _addcarry_u64(carry_flag, a, b, c);
用于添加:

我们可以使用

在MSVC中,我们可以使用内部函数(也可以在其他位大小中使用)

下面是它的使用方法:

// *c = a - (b + borrow)
// borrow_flag is set to 1 if (a < (b + borrow))
borrow_flag = _subborrow_u64(borrow_flag, a, b, c);
// *c = a + b + carry
// carry_flag is set to 1 if there is a carry bit
carry_flag = _addcarry_u64(carry_flag, a, b, c);
以下是我们如何将其应用于您的情况

uint64_t sub_no_underflow(uint64_t a, uint64_t b){
    uint64_t result;
    borrow_flag = _subborrow_u64(0, a, b, &result);
    return result * !borrow_flag;
}
uint64_t add_no_overflow(uint64_t a, uint64_t b){
    uint64_t result;
    carry_flag = _addcarry_u64(0, a, b, &result);
    return !carry_flag * result - carry_flag;
}
我不像减法那个样喜欢这个,但我觉得它很漂亮


如果加法溢出,
carry\u flag=1
。如果不使用ing
carry\u flag
则产生0,因此当溢出时,
!carry\u flag*result=0
。由于
0-1
将无符号整数值设置为其最大值,因此如果没有进位,函数将返回加法的结果,并返回所选整数的最大值如果有进位,则为gral值。

y^((x^y)和-(x
对于
int
类型计算
min(x,y)这是一个最终的解决方案的一部分,基于目前的情况,也许是有帮助的。这是C还是C++问题?请选择一个。@ AlcAMAMBELL。它需要携带吗?因为如果你在看一个特定的体系结构,那么可能有一个很好的单指令。我知道ARM已经有了。对字节进行饱和向量加减运算。在X86上,
\u mm\u adds\u epi8
内在函数将在一条指令中执行16字节的饱和加法。我修复了(明显的?)输入错误,但我仍然不认为这是正确的。这还包括分支。我将删除这个答案,只是在汇编中的一个简单问题,不进行优化。三元运算符和if/else语句之间的区别是什么?@GRC没有区别。@GRC FUZxxl是正确的,但一如既往,请您自己尝试。即使您不知道汇编(你可以在这里提问,如果你不清楚的话),朱
uint64_t add_no_overflow(uint64_t a, uint64_t b){
    uint64_t result;
    carry_flag = _addcarry_u64(0, a, b, &result);
    return !carry_flag * result - carry_flag;
}