Как сделать неподписанное дополнение насыщения в C?

Указатель NULL - это тот, который указывает на никуда. Когда вы разыскиваете указатель p, вы говорите «дайте мне данные в месте, хранящемся в« p ». Когда p является нулевым указателем, местоположение, хранящееся в p, является nowhere, вы говорите «Дайте мне данные в месте« нигде ». Очевидно, он не может этого сделать, поэтому он выбрасывает NULL pointer exception.

В общем, это потому, что что-то не было правильно инициализировано.

40
задан Peter Cordes 10 June 2019 в 17:24
поделиться

11 ответов

Вы, вероятно, хотите портативный C код здесь, который Ваш компилятор превратится в надлежащий блок ARM. ARM имеет условные перемещения, и они могут быть условным выражением на переполнении. Алгоритм тогда становится, добавляют, и условно устанавливают место назначения на неподписанный (-1), если переполнение было обнаружено.

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;
}

Примечание, что это отличается от других алгоритмов, в которых оно исправляет переполнение, вместо того, чтобы полагаться на другое вычисление для обнаружения переполнения.

x86-64 лязгают 3.7 выводами-O3 для adds32: значительно лучше, чем какой-либо другой ответ:

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

ARMv7: gcc 4.8 -O3 -mcpu=cortex-a15 -fverbose-asm вывод для adds32:

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

16 битов: все еще не использует неподписанное насыщение 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  @
19
ответ дан Amir A. Shabani 27 November 2019 в 01:13
поделиться

Я предполагаю, лучший способ для x86 состоит в том, чтобы использовать встроенный ассемблер для проверки флага переполнения после дополнения. Что-то как:

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

Это не очень портативно, но по моему скромному мнению самый эффективный путь.

1
ответ дан Igor Semenov 27 November 2019 в 01:13
поделиться

Лучшая производительность будет обычно включать встроенный ассемблерный код (как некоторые уже заявили).

, Но для портативного C, эти функции только включают одно сравнение и никакое преобразование типа (и таким образом я верю оптимальный):

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;
}

Как макросы, они становятся:

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

я оставляю версии для 'неподписанного долгими' и 'неподписанный длинный длинный' как осуществление читателю.;-)

2
ответ дан Kevin 27 November 2019 в 01:13
поделиться

Я не уверен, быстрее ли это, чем решение Skizz (всегда профиль), но здесь является альтернативным решением для блока без ответвлений. Обратите внимание, что это требует условного перемещения (CMOV) инструкция, которая я не уверен, доступно на Вашей цели.


uint32_t sadd32(uint32_t a, uint32_t b)
{
    __asm
    {
        movl eax, a
        addl eax, b
        movl edx, 0xffffffff
        cmovc eax, edx
    }
}
3
ответ дан Adam Rosenfield 27 November 2019 в 01:13
поделиться
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 */

Редактирование: Теперь, когда Вы отправили свою версию, я не уверен, что моя - любой более чистый/лучше/больше эффективный/больше studly.

7
ответ дан DGentry 27 November 2019 в 01:13
поделиться

Если Вы заботитесь о производительности, Вы действительно хотите сделать этот вид материала в SIMD, где x86 имеет собственную арифметику насыщения.

из-за этого отсутствия насыщения арифметики в скалярной математике, можно получить случаи, в которых операциях, сделанных на 4 переменных широких SIMD, [еще 112] , чем в 4 раза быстрее, чем эквивалентный C (и соответственно верный с 8 переменными широкими SIMD):

sub8x8_dct8_c: 1332 clocks
sub8x8_dct8_mmx: 182 clocks
sub8x8_dct8_sse2: 127 clocks
10
ответ дан Dark Shikari 27 November 2019 в 01:13
поделиться

В ARM Вы, возможно, уже насыщали встроенную арифметику. Расширения DSP ARMv5 могут насыщать регистры к любой длине в битах. Также на насыщенности ARM является обычно дешевым, потому что Вы можете экс-милый большая часть условного выражения инструкций.

ARMv6 даже насыщал дополнение, вычитание и все другие материал для 32 битов и упаковал числа.

На x86 Вы получаете влажную арифметику или через MMX или через SSE.

Всему этому нужен ассемблер, таким образом, это не то, что Вы попросили.

существуют C-приемы, чтобы сделать насыщаемую арифметику также. Этот небольшой код делает насыщаемое дополнение на четырех байтах dword. Это основано на идее вычислить 32 полусумматора параллельно, например, добавляющие числа без переполнения переноса.

Это сделано сначала. Тогда переносы вычисляются, добавляются и заменяются маской, если дополнение переполнилось бы.

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;
}

можно получить то же для 16 битов (или любой вид битового поля) путем изменения signmask константы и сдвигов в нижней части как это:

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;
}

Выше кода делает то же для 16 и 32 битовых значений.

, Если Вам не нужна функция, что функции добавляют и насыщают несколько значений в параллели просто, кашируют биты, в которых Вы нуждаетесь. На ARM Вы также хотите изменить signmask константу, потому что ARM не может загрузить все возможные константы на 32 бита в единственном цикле.

Редактирование: параллельные версии, скорее всего, медленнее, чем прямые методы, но они быстрее, если необходимо насыщать больше чем одно значение за один раз.

11
ответ дан Nils Pipenbrinck 27 November 2019 в 01:13
поделиться

В IA32 без условных переходов:

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
}
18
ответ дан Cole Johnson 27 November 2019 в 01:13
поделиться

В плоскости C:

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;} 

, который является почти macro-ized и непосредственно передает значение.

24
ответ дан Remo.D 27 November 2019 в 01:13
поделиться

Текущая реализация, которую мы используем:

#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)))
2
ответ дан Frank Szczerba 27 November 2019 в 01:13
поделиться

Решение с нулевым переходом:

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

Хороший компилятор оптимизирует это, чтобы избежать каких-либо фактических 64-битных арифметических действий ( s >> 32 просто - флаг переноса, а - (s >> 32) - результат sbb% eax,% eax ).

В x86 asm (синтаксис AT&T, a и b в eax и ebx , результат в eax ]):

add %eax,%ebx
sbb %eax,%eax
or %ebx,%eax

8- и 16-битные версии должны быть очевидны. Подписанная версия может потребовать немного больше работы.

10
ответ дан 27 November 2019 в 01:13
поделиться
Другие вопросы по тегам:

Похожие вопросы: