Imagine que tenho dois bytes não assinados b
e x
. Preciso calcular bsub
como b - x
e badd
como b + x
. No entanto, não quero que ocorra underflow / overflow durante essas operações. Por exemplo (pseudocódigo):
b = 3; x = 5;
bsub = b - x; // bsub must be 0, not 254
e
b = 250; x = 10;
badd = b + x; // badd must be 255, not 4
A maneira óbvia de fazer isso inclui ramificação:
bsub = b - min(b, x);
badd = b + min(255 - b, x);
Só me pergunto se há alguma maneira melhor de fazer isso, ou seja, por meio de algumas manipulações de bits?
y ^ ((x ^ y) & -(x < y))
paraint
tipos avaliamin(x, y)
sem ramificação. Isso pode fazer parte de uma solução eventual, com base no que você tem até agora._mm_adds_epi8
intrínseco fará uma adição saturante de 16 bytes em uma única instrução.Respostas:
O artigo Branchfree Saturating Arithmetic fornece estratégias para isso:
Sua solução de adição é a seguinte:
u32b sat_addu32b(u32b x, u32b y) { u32b res = x + y; res |= -(res < x); return res; }
modificado para uint8_t:
uint8_t sat_addu8b(uint8_t x, uint8_t y) { uint8_t res = x + y; res |= -(res < x); return res; }
e sua solução de subtração é:
u32b sat_subu32b(u32b x, u32b y) { u32b res = x - y; res &= -(res <= x); return res; }
modificado para uint8_t:
uint8_t sat_subu8b(uint8_t x, uint8_t y) { uint8_t res = x - y; res &= -(res <= x); return res; }
fonte
template<class T>struct sat{T t;};
com operadores sobrecarregados que saturam? Uso adequado de namespaces. Principalmente açúcar.Um método simples é detectar o estouro e redefinir o valor conforme abaixo
O GCC pode otimizar a verificação de estouro em uma atribuição condicional ao compilar com -O2.
Eu medi o quanto de otimização comparando com outras soluções. Com mais de 1.000.000 de operações no meu PC, esta solução e a de @ShafikYaghmour tiveram em média 4,2 segundos, e a de @chux em média 4,8 segundos. Esta solução também é mais legível.
fonte
Para subtração:
diff = (a - b)*(a >= b);
Adição:
sum = (a + b) | -(a > (255 - b))
Evolução
// sum = (a + b)*(a <= (255-b)); this fails // sum = (a + b) | -(a <= (255 - b)) falis too
Obrigado a @R_Kapp
Graças a @NathanOliver
Este exercício mostra o valor de simplesmente codificar.
sum = b + min(255 - b, a);
fonte
sum
talvez(a + b) | -(a <= (255 - b))
?sum = ((a + b) | (!!((a + b) & ~0xFF) * 0xFF)) & 0xFF
, supondosizeof(int) > sizeof(unsigned char)
, mas isso parece tão complexo que não sei se você ganharia alguma coisa com isso (além de dor de cabeça).(a+b+1)*(a <= (255-b)) - 1
.sub
foi tão fácil quanto o era0
. Mas outros limites apresentam complicações e seguem o comentário do usuário 2079303 .Se você estiver usando uma versão recente o suficiente do gcc ou clang (talvez também alguns outros), você pode usar embutidos para detectar estouro.
if (__builtin_add_overflow(a,b,&c)) { c = UINT_MAX; }
fonte
Para adição:
unsigned temp = a+b; // temp>>8 will be 1 if overflow else 0 unsigned char c = temp | -(temp >> 8);
Para subtração:
unsigned temp = a-b; // temp>>8 will be 0xFF if neg-overflow else 0 unsigned char c = temp & ~(temp >> 8);
Não são necessários operadores de comparação ou multiplicações.
fonte
Se você estiver disposto a usar assembly ou intrínseco, acho que tenho uma solução ideal.
Para subtração:
Podemos usar a
sbb
instruçãoNo MSVC, podemos usar a função intrínseca _subborrow_u64 (também disponível em outros tamanhos de bits).
É assim que é usado:
// *c = a - (b + borrow) // borrow_flag is set to 1 if (a < (b + borrow)) borrow_flag = _subborrow_u64(borrow_flag, a, b, c);
Aqui está como poderíamos aplicá-lo à sua situação
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; }
Para adição:
Podemos usar a
adcx
instruçãoNo MSVC, podemos usar a função intrínseca _addcarry_u64 (também disponível em outros tamanhos de bits).
É assim que é usado:
// *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);
Aqui está como poderíamos aplicá-lo à sua situação
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; }
Eu não gosto deste tanto quanto do de subtração, mas acho que é bem bacana.
Se o add estourar
carry_flag = 1
,. Not-ingcarry_flag
resulta em 0, portanto,!carry_flag * result = 0
quando há estouro. E uma vez0 - 1
que definirá o valor integral sem sinal para seu máximo, a função retornará o resultado da adição se não houver transporte e retornará o máximo do valor integral escolhido se houver transporte.fonte
que tal isso:
bsum = a + b; bsum = (bsum < a || bsum < b) ? 255 : bsum; bsub = a - b; bsub = (bsub > a || bsub > b) ? 0 : bsub;
fonte
Tudo pode ser feito em aritmética de bytes sem sinal
// Addition without overflow return (b > 255 - a) ? 255 : a + b // Subtraction without underflow return (b > a) ? 0 : a - b;
fonte
Se você quiser fazer isso com dois bytes, use o código mais simples possível.
Se você quiser fazer isso com 20 bilhões de bytes, verifique quais instruções vetoriais estão disponíveis em seu processador e se elas podem ser usadas. Você pode descobrir que seu processador pode fazer 32 dessas operações com uma única instrução.
fonte
Você também pode usar a biblioteca numérica segura no Boost Library Incubator . Ele fornece substitutos imediatos para int, long, etc ... que garantem que você nunca terá um estouro, estouro negativo, etc.
fonte
Se você chamar muito esses métodos, a maneira mais rápida não seria a manipulação de bits, mas provavelmente uma tabela de consulta. Defina uma matriz de comprimento 511 para cada operação. Exemplo para menos (subtração)
static unsigned char maxTable[511]; memset(maxTable, 0, 255); // If smaller, emulates cutoff at zero maxTable[255]=0; // If equal - return zero for (int i=0; i<256; i++) maxTable[255+i] = i; // If greater - return the difference
A matriz é estática e inicializada apenas uma vez. Agora sua subtração pode ser definida como método inline ou usando o pré-compilador:
#define MINUS(A,B) maxTable[A-B+255];
Como funciona? Bem, você deseja pré-calcular todas as subtrações possíveis para caracteres não assinados. Os resultados variam de -255 a +255, total de 511 resultados diferentes. Definimos uma matriz de todos os resultados possíveis, mas como em C não podemos acessá-la a partir de índices negativos, usamos +255 (em [A-B + 255]). Você pode remover esta ação definindo um ponteiro para o centro da matriz.
const unsigned char *result = maxTable+255; #define MINUS(A,B) result[A-B];
use-o como:
bsub = MINUS(13,15); // i.e 13-15 with zero cutoff as requested
Observe que a execução é extremamente rápida. Apenas uma subtração e uma deferência de ponteiro para obter o resultado. Sem ramificação. Os arrays estáticos são muito curtos, então eles serão totalmente carregados no cache da CPU para acelerar ainda mais o cálculo
O mesmo funcionaria para adição, mas com uma tabela um pouco diferente (os primeiros 256 elementos serão os índices e os últimos 255 elementos serão iguais a 255 para emular o corte além de 255.
Se você insiste na operação de bits, as respostas que usam (a> b) estão erradas. Isso ainda pode ser implementado como ramificação. Use a técnica do bit de sinal
// (num1>num2) ? 1 : 0 #define is_int_biggerNotEqual( num1,num2) ((((__int32)((num2)-(num1)))&0x80000000)>>31)
Agora você pode usá-lo para cálculo de subtração e adição.
Se você quiser emular as funções max (), min () sem ramificação, use:
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)); }
Meus exemplos acima usam inteiros de 32 bits. Você pode alterá-lo para 64, embora eu acredite que os cálculos de 32 bits sejam um pouco mais rápidos. Você decide
fonte
(x > y)
, não tem ramos.