Например:
f(8)=8
f(9)=8
Можно ли сделать x = x/2*2;
?
Есть ли риск того, что компилятор оптимизирует это выражение?
Например:
f(8)=8
f(9)=8
Можно ли сделать x = x/2*2;
?
Есть ли риск того, что компилятор оптимизирует это выражение?
Компилятор разрешает делать любые оптимизации, которые ему нравятся, пока он не вводит никаких побочных эффектов в программу. В вашем случае он не может отменить "2", как тогда выражение будет иметь другое значение для нечетных чисел.
x / 2 * 2
оценивается строго как (x / 2) * 2
, а x / 2
выполняется в целочисленной арифметике, если x
является интегральным типом.
Это, по сути, является идиоматическим методом округления.
Поскольку вы указали целые числа без знака, вы можете сделать это с помощью простой маски:
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)
C и С++ обычно используют правило "как будто" в оптимизации. Результат вычисления должен быть таким, как если бы компилятор не оптимизировал ваш код.
В этом случае 9/2*2=8
. Компилятор может использовать любой метод для достижения результата 8. Это включает в себя битмаски, сдвиги бит или любой хакер, зависящий от процессора, который дает те же результаты (у x86 есть довольно много трюков, которые полагаются на то, что они не различают указатели и целые числа, в отличие от C и С++).
Вы можете написать 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
.
Если ваши значения имеют какой-либо неподписанный тип, как вы говорите, самым простым является
x & -2;
Чудеса беззнаковой арифметики делают так, что -2
преобразуется в тип x
и имеет битовый шаблон, который имеет все, но для младшего значащего бита, который равен 0
.
В отличие от некоторых других предлагаемых решений, это должно работать с любым беззнаковым целочисленным типом, который по крайней мере имеет ширину, чем unsigned
. (И вы не должны делать арифметику с более узкими типами.)
Дополнительный бонус, как отмечено supercat, использует только преобразование подписанного типа в неподписанный тип. Это стандартно определяется по модулю арифметики. Таким образом, результат всегда UTYPE_MAX-1
для UTYPE
неподписанного типа x
.
В частности, он не зависит от знакового представления платформы для подписанных типов.
Один из вариантов, который меня удивляет, пока не упоминается, заключается в использовании оператора modulo. Я бы сказал, что это означает ваше намерение, по крайней мере, так же, как ваш оригинальный фрагмент, и, возможно, даже лучше.
x = x - x % 2
Как утверждали другие, оптимизатор компилятора будет иметь дело с любым разумным выражением эквивалентно, поэтому будьте осторожны, что более ясное, а не то, что вы считаете самым быстрым. Все ответы на мелочи интересны, но вы не должны использовать никого из них вместо арифметических операторов (предполагая, что мотивация является арифметикой, а не настройкой бит).
просто используйте следующее:
template<class T>
inline T f(T v)
{
return v & (~static_cast<T>(1));
}
Не бойтесь, что это функция, компилятор должен, наконец, оптимизировать это v и (~ 1) с соответствующим типом 1.