Pergunta

O que é a melhor maneira (mais limpo, mais eficiente) a escrever saturando disso em C?

A função ou macro deve adicionar duas entradas não assinados (necessidade ambos os 16 e 32 bits versões) e retornar todos os bits de um (0xFFFF ou 0xFFFFFFFF), se a soma transborda.

Target é x86 e ARM usando gcc (4.1.2) e Visual Studio (apenas para simulação, assim que uma implementação de fallback é OK lá).

Foi útil?

Solução

Você provavelmente quer código C portátil aqui, que seu compilador vai se transformar em ARM adequada montagem. ARM tem movimentos condicionais, e estes podem ser condicionada ao transbordamento. O algoritmo torna-se então add, e condicionalmente definir o destino para unsigned (-1) se foi detectado excesso.

uint16_t add16(uint16_t a, uint16_t b)
{
  uint16_t c = a + b;
  if (c<a) /* Can only happen due to overflow */
    c = -1;
  return c;
}

Note que isso difere de outros algoritmos em que ele corrige excesso, em vez de confiar em um outro cálculo de detectar estouro.

x86-64 tinido 3,7 -O3 saída para adds32 : significativamente melhor do que qualquer outra resposta:

    add     edi, esi
    mov     eax, -1
    cmovae  eax, edi
    ret

ARMv7: gcc 4.8 -O3 -mcpu=cortex-a15 -fverbose-asm saída para adds32 :

    adds    r0, r0, r1      @ c, a, b
    it      cs
    movcs   r0, #-1         @ conditional-move
    bx      lr

16bit: ainda não usa instruções add unsigned-saturando da ARM (UADD16)

    add     r1, r1, r0        @ tmp114, a
    movw    r3, #65535      @ tmp116,
    uxth    r1, r1  @ c, tmp114
    cmp     r0, r1    @ a, c
    ite     ls        @
    movls   r0, r1        @,, c
    movhi   r0, r3        @,, tmp116
    bx      lr  @

Outras dicas

Em C simples:

uint16_t sadd16(uint16_t a, uint16_t b)
    { return (a > 0xFFFF - b) ? 0xFFFF : a + b; }

uint32_t sadd32(uint32_t a, uint32_t b)
    { return (a > 0xFFFFFFFF - b) ? 0xFFFFFFFF : a + b;} 

que é quase macro-ized e directamente transmite o significado.

Na IA32 sem saltos condicionais:

uint32_t sadd32(uint32_t a, uint32_t b)
{
#if defined IA32
  __asm
  {
    mov eax,a
    xor edx,edx
    add eax,b
    setnc dl
    dec edx
    or eax,edx
  }
#elif defined ARM
  // ARM code
#else
  // non-IA32/ARM way, copy from above
#endif
}

aritmética Em ARM você pode já ter saturado embutido. Os ARMv5 DSP-extensões podem saturar registradores para qualquer bit de comprimento. Também na saturação ARM é geralmente mais barato porque você pode excute a maioria das instruções condicional.

ARMv6 mesmo saturou adição, subtracção e todas as outras coisas para 32 bits e números embalados.

No x86 você ficar saturado aritmética, quer através MMX ou SSE.

Tudo isso necessidades assembler, por isso não é o que você pediu.

Existem C-truques para não saturadas aritmética bem. Este código não pouco além em quatro bytes de um DWORD saturado. É baseado na idéia de calcular 32 meio-somadores em paralelo, por exemplo, adicionando números sem carry estouro.

Isto é feito em primeiro lugar. Então o transporta são calculados, adicionado e substituído com uma máscara se a adição iria transbordar.

uint32_t SatAddUnsigned8(uint32_t x, uint32_t y) 
{
  uint32_t signmask = 0x80808080;
  uint32_t t0 = (y ^ x) & signmask;
  uint32_t t1 = (y & x) & signmask;
  x &= ~signmask;
  y &= ~signmask;
  x += y;
  t1 |= t0 & x;
  t1 = (t1 << 1) - (t1 >> 7);
  return (x ^ t0) | t1;
}

Você pode obter o mesmo para 16 bits (ou qualquer tipo de bit-campo), alterando a constante signmask e as mudanças na parte inferior como esta:

uint32_t SatAddUnsigned16(uint32_t x, uint32_t y) 
{
  uint32_t signmask = 0x80008000;
  uint32_t t0 = (y ^ x) & signmask;
  uint32_t t1 = (y & x) & signmask;
  x &= ~signmask;
  y &= ~signmask;
  x += y;
  t1 |= t0 & x;
  t1 = (t1 << 1) - (t1 >> 15);
  return (x ^ t0) | t1;
}

uint32_t SatAddUnsigned32 (uint32_t x, uint32_t y)
{
  uint32_t signmask = 0x80000000;
  uint32_t t0 = (y ^ x) & signmask;
  uint32_t t1 = (y & x) & signmask;
  x &= ~signmask;
  y &= ~signmask;
  x += y;
  t1 |= t0 & x;
  t1 = (t1 << 1) - (t1 >> 31);
  return (x ^ t0) | t1;
}
código

Acima faz o mesmo para 16 e 32 bit valores.

Se você não precisar do recurso que as funções de adicionar e saturar vários valores em paralelo apenas mascarar os bits que você precisa. Em ARM você também deseja alterar a constante signmask porque ARM não pode carregar todas as possíveis constantes de 32 bits em um único ciclo.

Editar: As versões paralelas são mais prováveis ??mais lento do que os métodos para a frente em linha reta, mas eles são mais rápidos se você tem que saturar mais de um valor de cada vez

.

Se você se preocupa com o desempenho, você realmente quer fazer esse tipo de coisa em SIMD, onde x86 tem aritmética de saturação nativa.

Devido a esta falta de saturar aritmética em matemática escalar, pode-se obter casos em que as operações feitas on-wide de 4 variáveis ??SIMD é mais de 4 vezes mais rápido do que o equivalente C (e, correspondentemente verdadeiro com-wide 8-variável SIMD):

sub8x8_dct8_c: 1332 clocks
sub8x8_dct8_mmx: 182 clocks
sub8x8_dct8_sse2: 127 clocks

solução ramo Zero:

uint32_t sadd32(uint32_t a, uint32_t b)
{
    uint64_t s = (uint64_t)a+b;
    return -(s>>32) | (uint32_t)s;
}

Um bom compilador irá otimizar isso para evitar fazer qualquer aritmética de 64 bits real (s>>32 será apenas a bandeira de transporte, e -(s>>32) é o resultado de sbb %eax,%eax).

Em asm x86 (AT & T sintaxe, a e b em eax e ebx, resultado em eax):

add %eax,%ebx
sbb %eax,%eax
or %ebx,%eax
versões

8 e 16 bits deveria ser óbvio. versão assinada pode exigir um pouco mais de trabalho.

uint32_t saturate_add32(uint32_t a, uint32_t b)
{
    uint32_t sum = a + b;
    if ((sum < a) || (sum < b))
        return ~((uint32_t)0);
    else
        return sum;
} /* saturate_add32 */

uint16_t saturate_add16(uint16_t a, uint16_t b)
{
    uint16_t sum = a + b;
    if ((sum < a) || (sum < b))
        return ~((uint16_t)0);
    else
        return sum;
} /* saturate_add16 */

Editar:. Agora que você postou sua versão, eu não tenho certeza meu é qualquer produto de limpeza / melhor / mais eficiente / mais studly

Eu não tenho certeza se este é mais rápido do que a solução da Skizz (sempre perfil), mas aqui está uma solução de montagem alternativa no-galho. Note que isto requer a instrução de movimentação condicional (CMOV), que eu não tenho certeza está disponível em seu alvo.


uint32_t sadd32(uint32_t a, uint32_t b)
{
    __asm
    {
        movl eax, a
        addl eax, b
        movl edx, 0xffffffff
        cmovc eax, edx
    }
}

A implementação atual que estamos usando é:

#define sadd16(a, b)  (uint16_t)( ((uint32_t)(a)+(uint32_t)(b)) > 0xffff ? 0xffff : ((a)+(b)))
#define sadd32(a, b)  (uint32_t)( ((uint64_t)(a)+(uint64_t)(b)) > 0xffffffff ? 0xffffffff : ((a)+(b)))

O melhor desempenho geralmente envolvem linha de montagem (como alguns já disse).

Mas para C portátil, estas funções envolvem apenas uma comparação e nenhum tipo de fundição (e, portanto, eu acredito ideal):

unsigned saturate_add_uint(unsigned x, unsigned y)
{
    if (y>UINT_MAX-x) return UINT_MAX;
    return x+y;
}

unsigned short saturate_add_ushort(unsigned short x, unsigned short y)
{
    if (y>USHRT_MAX-x) return USHRT_MAX;
    return x+y;
}

Como macros, tornam-se:

SATURATE_ADD_UINT(x, y) (((y)>UINT_MAX-(x)) ? UINT_MAX : ((x)+(y)))
SATURATE_ADD_USHORT(x, y) (((y)>SHRT_MAX-(x)) ? USHRT_MAX : ((x)+(y)))

Deixo versões para 'unsigned long' e 'unsigned long long' como um exercício para o leitor. ; -)

Apenas no caso de alguém quiser saber uma implementação sem ramificação usando 2 de inteiros complemento de 32 bits.

Warning! Esse código usa a operação indefinido: "deslocamento para a direita por -1" e, portanto, explora a propriedade do Intel Pentium SAL instrução para mascarar a contagem operando para 5 bits.

int32_t sadd(int32_t a, int32_t b){
    int32_t sum = a+b;
    int32_t overflow = ((a^sum)&(b^sum))>>31;
    return (overflow<<31)^(sum>>overflow);
 }

É a melhor implementação conhecido por mim

suponho, a melhor maneira para x86 é usar inline assembler para verificar bandeira estouro depois disso. Algo como:

add eax, ebx
jno @@1
or eax, 0FFFFFFFFh
@@1:
.......

Não é muito portátil, mas IMHO a forma mais eficiente.

Uma alternativa para a solução ramo livre x86 asm é (sintaxe AT & T, a e b em EAX e EBX, resultado em EAX):

add %eax,%ebx
sbb $0,%ebx

Usando C ++, você pode escrever uma variante mais flexível do Remo.D 's solução:

template<typename T>
T sadd(T first, T second)
{
    static_assert(std::is_integral<T>::value, "sadd is not defined for non-integral types");
    return first > std::numeric_limits<T>::max() - second ? std::numeric_limits<T>::max() : first + second;
}

Isto pode ser facilmente traduzidos para C - usando os limites definidos em limits.h. Observe também que a Largura fixa tipos inteiros pode não estiveram disponíveis em seu sistema.

//function-like macro to add signed vals, 
//then test for overlow and clamp to max if required
#define SATURATE_ADD(a,b,val)  ( {\
if( (a>=0) && (b>=0) )\
{\
    val = a + b;\
    if (val < 0) {val=0x7fffffff;}\
}\
else if( (a<=0) && (b<=0) )\
{\
    val = a + b;\
    if (val > 0) {val=-1*0x7fffffff;}\
}\
else\
{\
    val = a + b;\
}\
})

Eu fiz um teste rápido e parece trabalho, mas não extensivamente bateu ainda! Isso funciona com sinal de 32 bit. op: o editor usado na página web não me deixa postar uma macro ou seja, a sua não compreensão não-recuado sintaxe etc

!
int saturating_add(int x, int y)
{
    int w = sizeof(int) << 3;
    int msb = 1 << (w-1);

    int s = x + y;
    int sign_x = msb & x;
    int sign_y = msb & y;
    int sign_s = msb & s;

    int nflow = sign_x && sign_y && !sign_s;
    int pflow = !sign_x && !sign_y && sign_s;

    int nmask = (~!nflow + 1);
    int pmask = (~!pflow + 1);

    return (nmask & ((pmask & s) | (~pmask & ~msb))) | (~nmask & msb);
}

Esta implementação não usar o controle de fluxos, operadores campare (==, !=) eo operador ?:. Ele só usa operadores bit a bit e operadores lógicos.

Saturação aritmética não é padrão para C, mas é muitas vezes implementada via intrínsecos do compilador, então a forma mais eficiente não será o mais limpo. Você deve adicionar blocos #ifdef para selecionar a maneira correta. resposta das MSalters é o mais rápido para a arquitetura x86. Para ARM você precisa usar a função __qadd16 (compilador ARM) de _arm_qadd16 (Microsoft Visual Studio) para a versão de 16 bits e __qadd para a versão de 32 bits. Eles vão ser automaticamente traduzidos para uma instrução ARM.

Links:

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top