Существует ли однострочник для вычитания 1 из входного значения, если входное значение больше нуля?

Возможно, я забываю гениальную побитовую операцию, но есть ли более умный способ выразить следующее?

int r = foo(); // returns an int
r -= r > 0; // same as `if(r > 0) r--;`

Я хочу объединить строки в одно выражение.

2

Решение

Самый читаемый способ — самый умный:

if (r > 0)
--r;

Он читабелен, и, скорее всего, он создаст самый быстрый код, потому что многие разработчики не делают тщетных попыток выглядеть умно, а люди, создающие оптимизирующие компиляторы, обнаруживают общие шаблоны и оптимизируют их.

4

Другие решения

Лично я бы предпочел if (r > 0) r--; для удобства чтения, но если вы действительно хотите избежать условных выражений:

r = r > 0 ? r-1 : r;

который по-прежнему является условным присваиванием, либо:

r > 0 && r--;

или же:

r <= 0 || r--;

Это работает, потому что, если левая сторона уже определяет результат, правая сторона не будет выполнена.

Кстати, не ожидайте каких-либо различий в производительности. Большинство компиляторов будут генерировать более или менее одинаково производительный код из всех этих опций.

1

Если бы тест был r> = 0, то одной из возможностей было бы выделить бит знака, а затем вычесть (1-signBit).

В C что-то вроде этого (но я думаю, что это можно перенести на многие языки)

int r = foo();
return r - (int)((~ ((unsigned) r)) >> (sizeof(r)*8-1));

Конечно, теперь вы зависите от внутреннего представления int и сильно запутали код …

Для r> 0 это более сложно … Может быть, что-то вроде

unsigned int r = foo();
return (int) (r - ((~(r | (r-1))) >> (sizeof(r)*8-1)));

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

Если каждая такая незначительная операция, как эта, проходит через такой процесс, писать код будет больно, и даже больше для тех, кому придется его поддерживать (читать / понимать / расширять …). Я надеюсь, что это не преждевременная оптимизация, и в этом случае, возможно, правильным решением будет написать небольшой кусочек ассемблера.

0

Ну, скажем, для 32-битных целых чисел с 2-мя дополнениями, что-то подобное может работать (не проверено!)

const int mask = 1 << 31;

int r = foo();

r -= ((r ^ mask) >> 31);

в основном, вы проверяете, равен ли крайний левый бит нулю, и конвертируете его в 1, если он есть, в противном случае он все равно будет равен нулю, затем сдвигаете его, чтобы получить 1 для положительного и 0 для отрицательного, и вычесть

-1