Ответ 1
int even_number = (number / 2) * 2;
Это должно работать независимо от архитектуры, пока оптимизатор не собирается мешать (он не должен, но кто знает).
Иногда мне нужно быть уверенным, что некоторое целое число равно. Поэтому я мог бы использовать следующий код:
int number = /* magic initialization here */;
// make sure the number is even
if ( number % 2 != 0 ) {
number--;
}
но это не похоже на очень эффективный самый эффективный способ сделать это, поэтому я мог бы сделать следующее:
int number = /* magic initialization here */;
// make sure the number is even
number &= ~1;
но (к тому же, не будучи читаемым), я не уверен, что решение полностью переносимо.
note: этот код должен работать только с положительными целыми числами, но с решением, которое также работает с отрицательными числами, будет плюсом.
int even_number = (number / 2) * 2;
Это должно работать независимо от архитектуры, пока оптимизатор не собирается мешать (он не должен, но кто знает).
Лично я бы пошел с встроенной вспомогательной функцией.
inline int make_even(int n)
{
return n - n % 2;
}
// ....
int m = make_even(n);
Я бы использовал второе решение. В любом двоичном представлении, независимо от количества бит, бит-триана или мало-северного или других различий в архитектуре, эта операция приведет к установке минимального бита в ноль. Он быстрый и полностью портативный. Цель кода может быть объяснена с помощью комментариев, если вы встретите нищих программистов С, которые не могут понять, что это значит.
Прежде чем принять ответ, я сделаю свой собственный, который попытается обобщить и заполните некоторую информацию, найденную здесь:
Четыре возможных метода, которые описаны (и некоторые небольшие их варианты).
if (number % 2 != 0) {
number--;
}
number&= ~1
number = number - (number % 2);
number = (number / 2) * 2;
Прежде чем продолжить, позвольте мне уточнить: Ожидаемый выигрыш для использования любого из этих методов минимален, даже если бы мы могли доказать, что один метод на 200% быстрее, чем другие, худший из них настолько быстр что единственный способ получить видимый выигрыш в скорости будет, если бы этот метод был многократно вызываемых в приложении с привязкой к процессору. Таким образом, это больше упражнения для удовольствия, чем настоящая оптимизация.
Что касается читаемости, я бы оценил метод 1 как наиболее читаемый, метод 4 как второй лучший, а метод 2 - хуже. Люди могут не согласиться, но я оценил их так, потому что:
Имея это в виду, я бы добавил, что принято считать, что лучший способ
для реализации этого используется функция inline
, и ни один из параметров не является
эта нечитабельная читаемость не является проблемой (прямое использование в коде
ясны и понятны, и чтение метода никогда не будет таким трудным).
Если вы не хотите использовать метод inline
, я бы рекомендовал вам использовать
метод 1 или метод 4.
Было упомянуто, что метод 1 может быть опущен, в зависимости от того, как
процессор представляет целые числа. Чтобы убедиться, что вы можете добавить следующее
STATIC_ASSERT
при использовании метода 1.
STATIC_ASSERT(INT_MIN % 2 == 0, make_even_may_underflow);
Что касается метода 3, даже если INT_MIN
даже не может быть недостаточно
в зависимости от того, имеет ли результат тот же знак делителя или
дивиденды. Наличие одного и того же знака делителя никогда не происходит, потому что
INT_MIN - (-1)
ближе к 0.
Добавьте только STATIC_ASSERT
, чтобы убедиться:
STATIC_ASSERT(INT_MIN % 2 == 0 || -1 % 2 < 0, make_even_may_underflow);
Конечно, вы можете использовать эти методы, когда STATIC_ASSERT
терпит неудачу с тех пор
это будет проблемой только при передаче INT_MIN вашему методу make_even
но я бы настоятельно советовал ему.
При использовании метода 2 вы должны убедиться, что ваше представление битов компилятора ведет себя как ожидалось:
STATIC_ASSERT( (1 & ~1) == 0, unsupported_bit_representation);
// two complement OR sign-and-magnitude.
STATIC_ASSERT( (-3 & ~1) == -4 || (-3 & ~1) == -2 , unsupported_bit_representation);
Я также сделал некоторые наивные тесты скорости, используя утилиту Unix time
. Я побежал каждый
разного метода (и его вариаций) 4 раза и записал результаты,
поскольку результаты не сильно менялись, я не счел нужным проводить больше тестов.
Полученные результаты показывают метод 4 и метод 2 как самый быстрый из них все.
Согласно предоставленной информации, я бы рекомендовал использовать метод 4. Его читаемый, я не знаю о каких-либо проблемах совместимости и отлично работает.
Надеюсь, вам понравится этот ответ и используйте информацию, содержащуюся здесь, чтобы сделать ваш собственный осознанный выбор.:)
исходный код доступен, если вы хотите проверить мои результаты. пожалуйста, обратите внимание
что тесты, скомпилированные с помощью g++
и выполняемые в Mac OS X. Различные
платформы и компиляторы могут давать разные результаты.
Решение &=
выглядит лучше всего для меня. Если вы хотите сделать его более портативным и более читаемым:
const int MakeEven = -2;
int number = /* magic initialization here */
// Make sure number is even
number &= MakeEven;
Второе решение должно быть значительно быстрее первого. Он полностью переносится? Скорее всего, хотя, вероятно, есть компьютер где-то, что делает математику иначе.
Это должно работать для целых положительных и отрицательных чисел.
Используйте свое второе решение как встроенную функцию и ставьте утверждение assert в ее реализацию, чтобы документировать и тестировать, что она работает на платформе, на которой она скомпилирована.
BOOST_STATIC_ASSERT( (1 & ~1) == 0 );
BOOST_STATIC_ASSERT( (-1 & ~1) == -2 );
Ваше второе решение работает только в том случае, если ваше представление знака - "два дополнения" или "знак и величина". Чтобы сделать это на месте, я пошел бы с вариантом сузертерпатта, который должен (почти) всегда работать
number -= (number % 2);
Вы не знаете точно, в каком направлении это будет "круглым" для отрицательных значений, поэтому в крайних случаях у вас может быть недополнение.
Следующий подход прост и не требует умножения или деления.
number = number & ~1;
или
number = (number + 1) & ~1;