Если я хочу округлить целое число без знака до ближайшего меньшего или равного четного целого числа, могу ли я разделить на 2, а затем умножить на 2?

Например:

f(8)=8
f(9)=8

Можно ли сделать x = x/2*2;? Есть ли риск того, что компилятор оптимизирует это выражение?

Ответ 1

Компилятор разрешает делать любые оптимизации, которые ему нравятся, пока он не вводит никаких побочных эффектов в программу. В вашем случае он не может отменить "2", как тогда выражение будет иметь другое значение для нечетных чисел.

x / 2 * 2 оценивается строго как (x / 2) * 2, а x / 2 выполняется в целочисленной арифметике, если x является интегральным типом.

Это, по сути, является идиоматическим методом округления.

Ответ 2

Поскольку вы указали целые числа без знака, вы можете сделать это с помощью простой маски:

x & (~1u)

Который установит LSB в ноль, создав тем самым немедленное четное число, не превышающее x. То есть, если x имеет тип, который не шире, чем unsigned int.

Вы можете, конечно, заставить 1 быть того же типа, что и более широкий x, например:

x & ~((x & 1u) | 1u)

Но в этот момент вы действительно должны взглянуть на этот подход как упражнение в обфускации и принять ответ от Вирсавии.


Я, конечно, забыл о стандартной библиотеке. Если вы включаете stdint.h (или cstdint, как и в коде С++). Вы можете позволить внедрению заботиться о деталях:

uintmax_t const lsb = 1;
x & ~lsb;

или

x & ~UINTMAX_C(1)

Ответ 3

C и С++ обычно используют правило "как будто" в оптимизации. Результат вычисления должен быть таким, как если бы компилятор не оптимизировал ваш код.

В этом случае 9/2*2=8. Компилятор может использовать любой метод для достижения результата 8. Это включает в себя битмаски, сдвиги бит или любой хакер, зависящий от процессора, который дает те же результаты (у x86 есть довольно много трюков, которые полагаются на то, что они не различают указатели и целые числа, в отличие от C и С++).

Ответ 4

Вы можете написать x / 2 * 2, и компилятор создаст очень эффективный код для очистки младшего значащего бита, если x имеет неподписанный тип.

И наоборот, вы можете написать:

x = x & ~1;

Или, возможно, менее читаемо:

x = x & -2;

Или даже

x = (x >> 1) << 1;

Или это тоже:

x = x - (x & 1);

Или этот последний, предложенный supercat, работает для положительных значений всех целочисленных типов и представлений:

x = (x | 1) ^ 1;

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

Обратите внимание, что x & (~1u) не работает, если тип x больше, чем unsigned int. Это контр-интуитивная ловушка. Если вы настаиваете на использовании константы unsigned, вы должны написать x & ~(uintmax_t)1, поскольку даже x & ~1ULL завершится с ошибкой, если x имеет больший тип, чем unsigned long long. Хуже того, многие платформы теперь имеют целые типы, большие, чем uintmax_t, такие как __uint128_t.

Вот небольшой ориентир:

typedef unsigned int T;

T test1(T x) {
    return x / 2 * 2;
}

T test2(T x) {
    return x & ~1;
}

T test3(T x) {
    return x & -2;
}

T test4(T x) {
    return (x >> 1) << 1;
}

T test5(T x) {
    return x - (x & 1);
}

T test6(T x) {  // suggested by supercat
    return (x | 1) ^ 1;
}

T test7(T x) {  // suggested by Mehrdad
    return ~(~x | 1);
}

T test1u(T x) {
    return x & ~1u;
}

Как показал Руслан, тестирование Godbolt Compiler Explorer показывает, что для всех вышеперечисленных альтернатив gcc -O1 получается тот же точный код для unsigned int, но изменение типа T на unsigned long long показывает другой код для test1u.

Ответ 5

Если ваши значения имеют какой-либо неподписанный тип, как вы говорите, самым простым является

x & -2;

Чудеса беззнаковой арифметики делают так, что -2 преобразуется в тип x и имеет битовый шаблон, который имеет все, но для младшего значащего бита, который равен 0.

В отличие от некоторых других предлагаемых решений, это должно работать с любым беззнаковым целочисленным типом, который по крайней мере имеет ширину, чем unsigned. (И вы не должны делать арифметику с более узкими типами.)

Дополнительный бонус, как отмечено supercat, использует только преобразование подписанного типа в неподписанный тип. Это стандартно определяется по модулю арифметики. Таким образом, результат всегда UTYPE_MAX-1 для UTYPE неподписанного типа x. В частности, он не зависит от знакового представления платформы для подписанных типов.

Ответ 6

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

x = x - x % 2

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

Ответ 7

просто используйте следующее:

template<class T>
inline T f(T v)
{
    return v & (~static_cast<T>(1));
}

Не бойтесь, что это функция, компилятор должен, наконец, оптимизировать это   v и (~ 1) с соответствующим типом 1.