сделать целое число даже - PullRequest
13 голосов
/ 19 января 2011

Иногда мне нужно быть уверенным, что какое-то целое число четное.В качестве такового я мог бы использовать следующий код:

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;

но (кроме того, что он не читается), я не уверен, что решение полностью переносимо.

  • Какое решение вы считаете лучшим?
  • Является ли второе решение полностью переносимым?
  • Второе решение значительно быстрее первого?
  • Какие другие решения вы знаете для этой проблемы?
  • Что если я сделаю это внутри встроенного метода?Оно должно (теоретически) быть таким же быстрым, как эти решения, и читаемость больше не должна быть проблемой, делает ли это второе решение более жизнеспособным?

примечание: этот код должен работать толькос положительными целыми числами, но решение, которое также работает с отрицательными числами, будет плюсом.

Ответы [ 8 ]

31 голосов
/ 19 января 2011

Лично я бы использовал встроенную вспомогательную функцию.

inline int make_even(int n)
{
    return n - n % 2;
}

// ....

int m = make_even(n);
6 голосов
/ 19 января 2011
int even_number = (number / 2) * 2;

Это должно работать независимо от архитектуры, если оптимизатор не мешает (не должен, но кто знает).

6 голосов
/ 19 января 2011

Я бы использовал второе решение. В любом двоичном представлении, независимо от количества битов, от старшего к младшему до младшего порядкового номера или от других архитектурных различий, эта операция будет иметь значение установки младшего бита в ноль. Это быстро и полностью переносимо. Цель кода может быть объяснена с помощью комментариев, если вы встретите каких-либо плохих программистов на Си, которые не могут понять, что это значит.

4 голосов
/ 25 февраля 2011

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

Четыре возможных метода, где они описаны (и некоторые их небольшие вариации).

  1. if (number % 2 != 0) { number--; }
  2. number&= ~1
  3. number = number - (number % 2);
  4. number = (number / 2) * 2;

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

Анализ * * тысяча двадцать-один читаемость Что касается читабельности, я бы оценил метод 1 как наиболее читаемый, метод 4 как второй лучший и метод 2 как худший. Люди могут не соглашаться, но я оценил их следующим образом: В методе 1 максимально ясно, что если число нечетное, вы хочу вычесть из этого, чтобы сделать его даже. Метод 4 также очень явно, но я занял второе место, потому что в На первый взгляд вы можете подумать, что он ничего не делает, и только часть второй последний вы как "О ... целочисленное деление". Метод 2 и 3 почти эквивалентны с точки зрения читабельности, но многие люди не привыкли к побитовым операциям и поэтому я оценил метод 2 как тем хуже. Имея это в виду, я бы добавил, что общепризнанно, что лучший способ для реализации этого используется функция inline, и ни один из вариантов не что нечитаемость, читаемость на самом деле не проблема (прямое использование в коде Ясно и ясно, и чтение метода никогда не будет таким трудным). Если вы не хотите использовать inline метод, я бы порекомендовал вам использовать только метод 1 или метод 4 . Проблемы совместимости Underflow Было упомянуто, что метод 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 методу, но я настоятельно советую против этого. (Un) поддерживаемые битовые представления При использовании метода 2 вы должны убедиться, что ваше битовое представление компилятора ведет себя как ожидалось: STATIC_ASSERT( (1 & ~1) == 0, unsupported_bit_representation); // two's 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. Платформы и компиляторы могут давать разные результаты.

3 голосов
/ 19 января 2011

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

 BOOST_STATIC_ASSERT( (1 & ~1) == 0 );

 BOOST_STATIC_ASSERT( (-1 & ~1) == -2 );
3 голосов
/ 19 января 2011

Решение &= выглядит лучше всего для меня.Если вы хотите сделать его более портативным и более читабельным:

const int MakeEven = -2;

int number = /* magic initialization here */
// Make sure number is even
number &= MakeEven;

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

Это должно работать для целых положительных и отрицательных чисел.

1 голос
/ 19 января 2011

Ваше второе решение работает, только если ваше знаковое представление является «дополнением до двух» или «знак и величина». Чтобы сделать это на месте, я бы использовал вариант Сузтерпатта, который должен (почти) всегда работать

number -= (number % 2);

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

0 голосов
/ 19 января 2011

Следующий подход прост и не требует умножения или деления.

number = number & ~1;

или

number = (number + 1) & ~1;
...