Вы бы использовали num% 2 или num & 1, чтобы проверить, четное ли число?

Что ж, есть как минимум два низкоуровневых способа определения четности данного числа:

 1. if (num%2 == 0) { /* even */ } 
 2. if ((num&1) == 0) { /* even */ }

Я считаю второй вариант гораздо более элегантным и значимым, и я обычно его использую. Но дело не только в вкусе; Фактическая производительность может отличаться: обычно побитовые операции (такие как logial-and here) намного эффективнее, чем операция mod (или div). Конечно, вы можете возразить, что некоторые компиляторы все равно смогут его оптимизировать, и я согласен ... но некоторые нет.

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

Что вы думаете?

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


person rmn    schedule 22.12.2009    source источник
comment
Используйте тот, который легче всего читать. Вы не должны заботиться о производительности. Это работа компилятора. Готов поспорить, что после оптимизации получившаяся сборка будет точно такой же.   -  person Martin York    schedule 23.12.2009
comment
Я просто, хотя (& 1) может не работать с отрицательными числами. Это будет зависеть от того, использует ли реализация 1-комплимент или 2-комплимент.   -  person Martin York    schedule 23.12.2009
comment
Мартин, ты прав по обоим пунктам. Фактический результат / и% также определяется реализацией, если хотя бы один аргумент отрицательный. Хотя в этом случае все нормально. C ++ 0x примет правило C99, согласно которому целочисленное деление всегда округляется до нуля.   -  person sellibitze    schedule 23.12.2009
comment
Я предполагаю, что все возненавидят меня за то, что я даже предложил (! (N% 2)) {;}   -  person silentcontributor    schedule 23.12.2009
comment
это, вероятно, только принесет пользу всем, если эти программисты найдут такое короткое время, чтобы понять утверждения такого рода. Именно такие представления приводят к эзотерическому неподдерживаемому коду. Практически всегда лучше использовать более удобочитаемый подход, чем подход с более высокой производительностью.   -  person RJFalconer    schedule 23.12.2009
comment
Есть ли какой-нибудь компилятор, написанный после 1980 года, который не генерировал бы один и тот же код для двух операторов? (предлагается дополнение до двух, а те, конечно, не будут, но действительно ли есть компиляторы / чипы, которые не используют дополнение до двух?)   -  person erikkallen    schedule 23.12.2009
comment
Дубликат: stackoverflow.com/questions/160930/   -  person Adam Rosenfield    schedule 23.12.2009
comment
num & 1 также не работает с недвоичными числами.   -  person ctrl-alt-delor    schedule 21.05.2012
comment
Возможный дубликат Как проверить, является ли целое число четное или нечетное?   -  person S.S. Anne    schedule 13.09.2019


Ответы (12)


Если вы собираетесь сказать, что некоторые компиляторы не будут оптимизировать %2, то вам также следует отметить, что некоторые компиляторы используют представление с дополнением единиц для целых чисел со знаком. В этом представлении &1 дает неправильный ответ для отрицательных чисел.

Так что же вы хотите - код, который работает медленно на «некоторых компиляторах», или код, который работает неправильно на «некоторых компиляторах»? Не обязательно использовать одинаковые компиляторы в каждом случае, но оба вида встречаются крайне редко.

Конечно, если num относится к беззнаковому типу или к одному из целочисленных типов фиксированной ширины C99 (int8_t и т. Д., Которые должны быть дополнением до 2), то это не проблема. В этом случае я считаю %2 более элегантным и значимым, а &1 - хакерским приемом, который иногда может быть необходим для повышения производительности. Я думаю, например, что CPython не выполняет эту оптимизацию, и то же самое будет верно для полностью интерпретируемых языков (хотя тогда накладные расходы на синтаксический анализ, вероятно, затмевают разницу между двумя машинными инструкциями). Я был бы немного удивлен, если бы встретил компилятор C или C ++, который не делал этого там, где это было возможно, потому что это не проблема в момент выдачи инструкций, если не раньше.

В общем, я бы сказал, что в C ++ вы полностью зависите от способности компилятора оптимизировать. Стандартные контейнеры и алгоритмы имеют n уровней косвенного обращения, большинство из которых исчезает, когда компилятор завершает встраивание и оптимизацию. Приличный компилятор C ++ может обрабатывать арифметические операции с постоянными значениями перед завтраком, а неподходящий компилятор C ++ будет создавать мусорный код, что бы вы ни делали.

person Steve Jessop    schedule 22.12.2009
comment
Целочисленное представление обычно определяется архитектурой хоста, а не компилятором. У вас может быть компилятор, который компилируется для машин, которые используют одно или два дополнения ... авторы компилятора будут решать, основываясь на доступном оборудовании (если им действительно просто не нравится скорость). Кроме того, вы никогда не увидите одну из этих машин, потому что вы не кодируете компьютеры, выпущенные до 1970 года. Единственное место, где вы действительно можете увидеть свое дополнение сегодня, - это контрольные суммы IP. - person Todd Gamblin; 23.12.2009
comment
Это определяется реализацией, для которой я использую неофициальный термин «компилятор». Компилятор-писатель принимает решение, основываясь на целевой архитектуре. Если мы говорим только о том, что делают настоящие общие компиляторы, которые я, вероятно, буду использовать, то все они выполняют оптимизацию. Таким образом, нет разницы в производительности, это так же верно, как целые числа - это два дополнения, и все сводится к вкусу / стилю / ясности. - person Steve Jessop; 23.12.2009
comment
Компилятор - это не неофициальный термин для обозначения реализации. - person Todd Gamblin; 23.12.2009
comment
Это. Может быть, вы не хотите, чтобы это было, но если хотите, я буду сообщать вам каждый раз, когда вижу, что кто-то говорит, что это зависит от компилятора для чего-то, что зависит от реализации, и вы можете провести остаток своей жизни 24/7. исправляя их все ;-). В любом случае, в этом случае подписанное представление зависит от реализации, и, как вы правильно заметили, компилятор может делать все, что захочет, независимо от целевой архитектуры. Один вариант может быть намного быстрее другого. - person Steve Jessop; 23.12.2009
comment
Но хорошо, я полагаю, что иногда я должен говорить о некоторых компиляторах, а не о некоторых компиляторах. GCC на одной цели будет делать разные вещи, определяемые реализацией, чем GCC на другой цели, но для многих целей это все равно тот же компилятор. Но все, что зависит от реализации, решает компилятор. Даже если для данной цели существует только один быстрый вариант, стандарт не требует, чтобы компилятор использовал этот вариант. - person Steve Jessop; 23.12.2009
comment
На самом деле стандарт для Java ДЕЙСТВИТЕЛЬНО требует, чтобы целые числа были дополнением до двух. Однако это вопрос C ++, а для C / C ++ это вопрос реализации. Я просто говорю, что разумные разработчики языка принимают это решение на основе того, что понимает оборудование. Если вы действительно ненавидите говорить, что целочисленное представление во многом определяется архитектурой хоста, то почему бы просто не сказать «реализация» вместо компилятора? - person Todd Gamblin; 23.12.2009
comment
Честно говоря, не уверен, наверное, это какая-то лень. Я не ненавижу это говорить, я просто не пытаюсь это сказать. Если я говорю строго о стандарте, то говорю о реализации. В противном случае я говорю «компилятор», потому что это то, с чем я взаимодействую напрямую. И я терзал то, что сказал спрашивающий, некоторые компиляторы все равно оптимизируют это, а не некоторые реализации, которые были бы более правильными. Думаю, я мог бы исправить это сейчас быстрее, чем спорить, я просто не думаю, что это настолько неправильно, чтобы требовать исправления ;-) - person Steve Jessop; 23.12.2009
comment
Это не должно было быть огромным дополнением - просто небольшая придирка :-). Я дал ответ +1, потому что согласился :-P. - person Todd Gamblin; 23.12.2009
comment
-1: Мне очень жаль, но если ваша архитектура делает это, то вы либо провалились через временную дыру в 1970-е годы, либо вы иначе узнаете об этом, так что это уже не вопрос. Откровенно говоря, компилятор с большей вероятностью откажется от ошибок, иначе логическое определение нечетных отрицательных чисел окажется непоследовательным, чем это, чтобы вас укусить. Я также хотел бы отметить, что я никогда не видел способа различать четные и нечетные отрицательные числа. Если число меньше нуля, то вряд ли оно будет представлять что-либо, для чего «странность» имеет смысл. разглагольствовать над kthanksbi - person James; 28.12.2009
comment
У меня нет архитектуры. Иногда мне хочется написать переносимый код по стандарту, который другие люди могут запускать на любой архитектуре, которая им нравится, без необходимости просматривать код построчно на предмет непереносимых идиом. Итак, если мой код работает только с целыми числами дополнения 2, то в примечаниях к переносу я задокументирую, что он работает только с целыми числами дополнения 2. Если вы когда-либо используете только одну архитектуру и компилятор, то, конечно, вам не нужно беспокоиться о стандарте. Но вопрос помечен как C ++, а не C ++ для какой-то конкретной архитектуры, поэтому следует остерегаться исключений. - person Steve Jessop; 30.12.2009

Сначала я кодирую для удобства чтения, поэтому мой выбор здесь num % 2 == 0. Это гораздо яснее, чем num & 1 == 0. Я позволю компилятору позаботиться об оптимизации за меня и буду настраивать его только в том случае, если профилирование покажет, что это узкое место. Все остальное преждевременно.

Считаю второй вариант гораздо более элегантным и содержательным.

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

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

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

И, что более важно, детали должны быть скрыты с помощью isEven метода. Таким образом, мы должны видеть if(isEven(someNumber)) { // details } и видеть num % 2 == 0 только один раз в определении isEven.

person jason    schedule 22.12.2009
comment
Верно. Если вы проверяете самый младший бит, мое первое предположение состоит в том, что вы тестируете флаг. - person Anon.; 23.12.2009
comment
Число равно даже потому, что его двоичное представление заканчивается определенным битом. В этом нет ничего плохого и ничто не делает это менее верным. - person Frunsi; 23.12.2009
comment
@frunsi - нет, это не так. Число - даже если% 2 не дает остатка. Я могу придумать множество реализаций, в которых число не заканчивается на LSB - например, 6502 выполняет 16-битную выборку. - person Martin Beckett; 23.12.2009
comment
@frunsi: Четное число - это число, которое делится на два без остатка. То есть число, которое делится на два с нулевым остатком. То есть число, сравнимое с нулем по модулю два. Определение четности не имеет ничего общего с представлением числа в определенной основе (скажем, тот факт, что оно заканчивается на 0, 2, 4, 6 или 8 в десятичном виде или 0 в двоичном). Это следствие определения, что младший бит четных чисел равен нулю. - person jason; 23.12.2009
comment
@Downvoter: Я уверен, что у вас есть веская причина. Мне было бы интересно это услышать. - person jason; 23.12.2009
comment
@frunsi: Кто сказал, что последний бит четного числа равен 0. А как насчет отрицательных чисел? Не будет ли последний бит зависеть от того, будет ли целочисленное представление комплиментом 1 или комплиментом 2. - person Martin York; 23.12.2009
comment
@ Джейсон: Я был противником, и вы, вероятно, уже проголосовали против меня. Я был неправ, и с добавлением дополнения к одному это тоже имеет смысл. Хотя ваши предыдущие рассуждения не были объективными, но мое мнение больше не имеет значения, я был неправ! ;) Я не говорю о дополнении на практике ... - person Frunsi; 23.12.2009
comment
Еще одна причина не рекомендовать бит и уловку: легко забыть () вокруг бит и выражения, как это сделал ответ ... - person L. F.; 13.09.2019

Я определяю и использую функцию «IsEven», поэтому мне не нужно думать об этом, затем я выбираю тот или иной метод и забываю, как я проверяю, является ли что-то четным.

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

person Doug T.    schedule 22.12.2009
comment
Забыть еще не значит безопасно. С модулем вы можете ничего не предполагать об отрицательных числах, но поведение в любом случае не определено! Вы безопаснее работать со всеми двумя дополнительными машинами. Modulo может работать для чисел с плавающей запятой, но дает неожиданные результаты из-за неточности, тогда как побитовая арифметика не определена и вызывает ошибку типа. - person Potatoswatter; 23.12.2009

Ваш вывод о производительности основан на популярной ложной предпосылке.

По какой-то причине вы настаиваете на переводе языковых операций в их «очевидные» машинные аналоги и делаете выводы о производительности на основе этого перевода. В этом конкретном случае вы пришли к выводу, что побитовая и & операция языка C ++ должна быть реализована с помощью машинной операции побитовой и, тогда как операция по модулю % должна каким-то образом включать машинное деление , что якобы медленнее. Такой подход имеет очень ограниченное применение, если вообще используется.

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

Когда дело доходит до таких базовых операций с непосредственной константой в качестве операнда, любой уважающий себя компилятор всегда сразу «поймет», что и num & 1, и num % 2 для интегрального num делают одно и то же, что заставит компилятор сгенерировать абсолютно идентичный код для оба выражения. Естественно, производительность будет точно такой же.

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

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

Другими словами, аргумент эффективности неверен.

Во-вторых, возвращаясь к исходному вопросу, более предпочтительным способом определения четности / нечетности значения, безусловно, является подход num % 2, поскольку он реализует требуемую проверку буквально («по определению») и четко выражает Дело в том, что проверка чисто математическая. Т.е. он дает понять, что мы заботимся о свойстве числа, а не о свойстве его представления (как это было бы в случае варианта num & 1).

Вариант num & 1 следует зарезервировать для ситуаций, когда вам нужен доступ к битам представления значения числа. Использование этого кода для проверки четности / нечетности - весьма сомнительная практика.

person AnT    schedule 22.12.2009
comment
Здесь вы делаете много предположений, не все из которых верны, но именно ваше отношение принесло вам -1. Это простой вопрос, вам не нужно убивать ОП. - person cyberconte; 23.12.2009
comment
Большинство сделанных мной утверждений слишком общие, чтобы их можно было назвать неправильными предположениями. Итак: извините, все, что я сказал, совершенно правильно. Если что-то кажется вам неправильным, вы должны быть более конкретными. Что касается отношения, я почти уверен, что вы воображаете что-то, чего нет. - person AnT; 23.12.2009
comment
А это простой вопрос ... :) Нет, это простой вопрос для тех, кто недостаточно глубоко разбирается в проблеме. - person AnT; 23.12.2009
comment
Согласитесь с @cyberconte. -1 за то, что был задницей. Компилятора C ++ не было в истории человечества? Действительно? Быть конкретными. Кроме того, я призываю вас назвать основную архитектуру, в которой для реализации этого используется инструкция иное, чем и или деление. - person Todd Gamblin; 23.12.2009
comment
@tgamblin: А? Я не знаю, знаете ли вы значение слова «специфический», но утверждения Ни один компилятор C ++ в истории человечества не был настолько конкретным, насколько это вообще возможно. - person AnT; 23.12.2009
comment
@tgamblin: По поводу архитектуры ... Сам вопрос не имеет смысла. Это не архитектура, которая реализует операции языка C ++, это компилятор, который реализует их, используя доступные средства, предоставляемые архитектурой оборудования. Назвать только один сложно, потому что они все работают так, как я описал. Ни один компилятор, который я когда-либо видел, не реализовал num % 2 через буквальное деление (зачем им это?). Но если нужна конкретная (правда, не вижу в этом смысла): X86 + GCC или MSVC. - person AnT; 23.12.2009
comment
Кроме того, X86 - это архитектура, в которой нечетность значения раскрывается через флаг PF CPU, что означает, что интеллектуальный компилятор может вообще не генерировать никаких инструкций, если значения были получены в результате последней операции. - person AnT; 23.12.2009
comment
И последнее: убедитесь, что с этого момента, когда вы почувствуете желание позвонить кому-то в ТАК задницу, вы сначала подумаете (желательно головой), сделайте глубокий вдох и сосчитайте до 10 перед публикацией. И нет, я не предлагаю это как вариант. - person AnT; 23.12.2009
comment
Во-первых, это простой вопрос с простым ответом. Это сложно только в том случае, если вы этого хотите. Во-вторых, повторяя свой последний пост, вы противоречите себе (большинство сделанных мною утверждений слишком общие, чтобы их можно было назвать неправильными предположениями. / Ни один компилятор C ++ в истории человечества не был настолько конкретным, насколько это возможно), вы пытаетесь чрезмерно компенсировать и принижать (это простой вопрос для тех, кто недостаточно глубоко разбирается в проблеме) и, как правило, грубые, полностью скрывая любые правильные утверждения, которые вы делаете. Предлагаю вам посмотреть в зеркало. - person cyberconte; 23.12.2009
comment
@cyberconte: Во-первых, именно так: это настолько сложно, насколько я хочу. Это мое время и мои усилия. Если я считаю, что этот вопрос требует более подробного объяснения, я предоставлю это объяснение. Это мое личное дело, а не ваше. Да, я понимаю, что ленивых обычно раздражают не такие ленивые люди, но я не собираюсь беспокоиться об этом, когда пишу свои ответы. - person AnT; 23.12.2009
comment
@cyberconte: Во-вторых, ваше обвинение в противоречии смехотворно: в моем ответе есть согласованные утверждения, в моем ответе есть общие утверждения. Есть оба вида. В то же время. Не знаю, сложно ли вам это понять. Фактически, единственная причина, по которой мы должны говорить об этом, заключается в том, что вы в своем первом комментарии предприняли широкую атаку, не указав никаких подробностей. - person AnT; 23.12.2009
comment
@cyberconte: Тон, присутствующий в моих дальнейших комментариях (ваше принижение), - это тон, заданный вами в ваших комментариях. Вы пытаетесь представить это как мою вину, но я могу только повторить, что вы вообразили что-то, чего изначально не было. - person AnT; 23.12.2009
comment
Я поставил +1 этому. Хорошее объяснение разницы между операциями значение и представление значения. Он также содержит аргумент прямого перехода и аргумент ЦП, который вам неизвестен. - person Johannes Schaub - litb; 23.12.2009
comment
@AndreyT: по поводу определения оптимизации, чье определение? Стандарт C ++ 03 говорит, например, что для оптимизации распределения пространства предоставляется специализация вектора для элементов типа bool. Очевидно, что он использует оптимизацию в более общем смысле, чем отклонение от абстрактной машины. Я бы посчитал, что написание кода в компиляторе для выбора более производительной инструкции в особом случае - это одна из форм оптимизации, но если нет, то как ее называть? Не то чтобы что-либо из этого умаляло ваш главный аргумент, поскольку, как бы мы это ни называли, мы знаем, что все компиляторы это делают. - person Steve Jessop; 28.04.2012
comment
@AnT: re: этот комментарий PF-флаг x86 здесь бесполезен. Большинство инструкций устанавливают его в соответствии с (горизонтальным) XOR младших 8 бит результата: PF = 1 означает четность en.wikipedia.org/wiki/Parity_flag. т.е. PF = ~popcnt((uint8_t)x) & 1. т.е. это en.wikipedia.org/wiki/Parity_bit. Все это бесполезно для другого распространенного использования термина "четность" = нечетное или четное. en.wikipedia.org/wiki/Parity_(mat Mathematics) - person Peter Cordes; 03.07.2019
comment
Возможно, стоит упомянуть, что x % 2 == 1 недешево для подписанного x, потому что -3 % 2 = -1, а не +1, поэтому системы дополнения 2 должны проверять знаковый бит, а также младший бит. (Если они не докажут, что x всегда неотрицательно). Так что да, x % 2 == 0 - это здорово, и мы должны рекомендовать, но есть проблема эффективности (и правильности), о которой следует беспокоиться со знаком по модулю, если вы не просто сравниваете с 0. - person Peter Cordes; 03.07.2019
comment
На самом деле в вашем ответе есть ошибка: вы утверждаете, что num & 1 и num % 2 эквивалентны. Это верно только при сравнении результата с нулем (и в системах с дополнением до 2 или знаками / величинами). Как показывает ответ Коди, до недавнего времени даже один из основных компиляторов x86 пропускал оптимизацию для x % 2 == 0. Но MSVC все равно отстой (особенно старый MSVC), поэтому я все равно поддержу этот ответ. :П - person Peter Cordes; 03.07.2019

Неоднократно упоминалось, что любой современный компилятор создаст одну и ту же сборку для обоих вариантов. Это напомнило мне демонстрационную страницу LLVM, которую я где-то видел на днях, поэтому решил, что дам ее тому назад. Я знаю, что это не более чем анекдотический случай, но он подтверждает то, что мы ожидали: x%2 и x&1 реализованы одинаково.

Я также попытался скомпилировать оба из них с помощью gcc-4.2.1 (gcc -S foo.c), и полученная сборка идентична (и вставлена ​​внизу этого ответа).

Запрограммируйте первое:

int main(int argc, char **argv) {
  return (argc%2==0) ? 0 : 1;
}

Результат:

; ModuleID = '/tmp/webcompile/_27244_0.bc'
target datalayout = "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32"
target triple = "i386-pc-linux-gnu"

define i32 @main(i32 %argc, i8** nocapture %argv) nounwind readnone {
entry:
    %0 = and i32 %argc, 1       ; <i32> [#uses=1]
    ret i32 %0
}

Запрограммируйте второе:

int main(int argc, char **argv) {
  return ((argc&1)==0) ? 0 : 1;
}

Результат:

; ModuleID = '/tmp/webcompile/_27375_0.bc'
target datalayout = "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32"
target triple = "i386-pc-linux-gnu"

define i32 @main(i32 %argc, i8** nocapture %argv) nounwind readnone {
entry:
    %0 = and i32 %argc, 1       ; <i32> [#uses=1]
    ret i32 %0
}

Вывод GCC:

.text
.globl _main
_main:
LFB2:
  pushq %rbp
LCFI0:
  movq  %rsp, %rbp
LCFI1:
  movl  %edi, -4(%rbp)
  movq  %rsi, -16(%rbp)
  movl  -4(%rbp), %eax
  andl  $1, %eax
  testl %eax, %eax
  setne %al
  movzbl  %al, %eax
  leave
  ret
LFE2:
  .section __TEXT,__eh_frame,coalesced,no_toc+strip_static_syms+live_support
EH_frame1:
  .set L$set$0,LECIE1-LSCIE1
  .long L$set$0
LSCIE1:
  .long 0x0
  .byte 0x1
  .ascii "zR\0"
  .byte 0x1
  .byte 0x78
  .byte 0x10
  .byte 0x1
  .byte 0x10
  .byte 0xc
  .byte 0x7
  .byte 0x8
  .byte 0x90
  .byte 0x1
  .align 3
LECIE1:
.globl _main.eh
_main.eh:
LSFDE1:
  .set L$set$1,LEFDE1-LASFDE1
  .long L$set$1
ASFDE1:
  .long LASFDE1-EH_frame1
  .quad LFB2-.
  .set L$set$2,LFE2-LFB2
  .quad L$set$2
  .byte 0x0
  .byte 0x4
  .set L$set$3,LCFI0-LFB2
  .long L$set$3
  .byte 0xe
  .byte 0x10
  .byte 0x86
  .byte 0x2
  .byte 0x4
  .set L$set$4,LCFI1-LCFI0
  .long L$set$4
  .byte 0xd
  .byte 0x6
  .align 3
LEFDE1:
  .subsections_via_symbols
person mrkj    schedule 22.12.2009
comment
Первоначальный вопрос пробудил мое болезненное любопытство, и я тоже немедленно проверил его (прежде чем увидел этот ответ). Функции с body return ((num & 1) == 0) и return ((num % 2) == 0), где num int или unsigned (всего четыре функции) все были преобразованы в одни и те же инструкции по сборке - я пробовал с x86_64, mips и arm, используя gcc версии 4.6.1, 4.4.1, 4.5.0 соответственно. - person FooF; 21.05.2012
comment
Я бы рекомендовал компилировать с включенной оптимизацией, чтобы упростить вывод gcc. И оставьте метаданные + отладочную информацию после EH_frame1:. Вы можете использовать godbolt.org, чтобы попробовать gcc / clang / ICC / MSVC для различных архитектур. - person Peter Cordes; 03.07.2019

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

ОДНАКО: у вашего лайнера есть ошибка.

Вы должны идти

if( (x&1) == 0 )

нет

if( x&1 == 0 )

Последний выполняет операцию AND x с 1 == 0, т. Е. Он выполняет операцию AND x с 0, получая 0, что, конечно, всегда считается ложным.

Итак, если вы сделали именно так, как вы предлагаете, все числа нечетные!

person Bill Forster    schedule 22.12.2009
comment
Я полагаю, что это одна из причин %2: приоритет % более интуитивно понятен в C. - person David Thornley; 23.12.2009
comment
Да, я считаю, что это одно правило приоритета, которое не соответствует моим ожиданиям, поэтому я всегда его отслеживаю. Однажды это сильно укусило меня, когда еще не было приличных отладчиков, и стоило черт знает сколько часов. Я заметил, что вопрос был незаметно отредактирован вскоре после того, как я опубликовал свой ответ. - person Bill Forster; 23.12.2009
comment
Черт возьми, я удивлен, что его не отредактировали, чтобы добавить скобки вокруг обоих выражений. Я считаю, что хорошей практикой является максимально явное определение приоритета, чтобы не заставлять человека, читающего код, догадываться о его значении. - person Adam; 23.12.2009
comment
Я тоже не хочу, чтобы читатели догадывались, но я не люблю слишком много заключать в скобки, когда правила приоритета дружественные. В этих случаях я показываю жесткую привязку с использованием пробелов. Например; если (RANGE_LO ‹= x && x‹ = RANGE_HI) z = x * 2 + y / 3; Никаких лишних парней, загромождающих вещи, и никакой путаницы в смысле. - person Bill Forster; 23.12.2009
comment
Я не рассчитывал, что формат комментария увеличит отступы в моем коде (в предыдущем комментарии), извините за это. - person Bill Forster; 23.12.2009
comment
Чтобы доказать, что ОП не имеет монополии на глупые ошибки, я просто заметил, что последнее слово моего ответа было совершенно неправильным (я сказал четное, а не нечетное). Теперь исправлено. - person Bill Forster; 24.12.2009
comment
Я бы хотел, чтобы в языках, производных от C, был бинарный оператор, который сообщал бы, было ли двоичное пересечение двух двоичных чисел ненулевым. В C можно просто написать if (x & 1), но такое использование запрещено во многих других языках, которые требуют bool вместо if. Если бы язык требовал bool для if, но перегрузил ! для проверки ненулевого значения целочисленного типа и ненулевого значения указателей, тогда, если бы (!! (x & 1)) `все равно было бы ИМХО лучше, чем if ((x & 1) != 0), если бы Цель кода не в том, чтобы проверить, равно ли выражение нулю, а в том, чтобы проверить его нулевое значение. - person supercat; 13.08.2015

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

Я бы сказал, что использование модуля по модулю упростит понимание, но создание функции is_even, использующей метод x & 1, дает вам лучшее из обоих миров.

person Peter Alexander    schedule 22.12.2009

Они оба довольно интуитивны.

Я бы дал небольшое преимущество num % 2 == 0, но на самом деле у меня нет предпочтений. Конечно, что касается производительности, это, вероятно, микрооптимизация, поэтому я бы не стал об этом беспокоиться.

person Jon Seigel    schedule 22.12.2009

Я потратил годы, настаивая на том, что любой разумный компилятор, стоящий занимаемого им места на диске, оптимизирует num % 2 == 0 до num & 1 == 0. Затем, проанализировав разборку по другой причине, у меня была возможность действительно проверить свое предположение.

Оказывается, я ошибался. Microsoft Visual Studio, вплоть до версии 2013, генерирует следующий объектный код для num % 2 == 0:

    and ecx, -2147483647        ; the parameter was passed in ECX
    jns SHORT $IsEven
    dec ecx
    or  ecx, -2
    inc ecx
$IsEven:
    neg ecx
    sbb ecx, ecx
    lea eax, DWORD PTR [ecx+1]

Да, в самом деле. Это в режиме выпуска со всеми включенными оптимизациями. Вы получаете практически одинаковые результаты при сборке для x86 или x64. Вы, наверное, не поверите мне; Я сам с трудом верил в это.

По сути, он делает то, что вы ожидаете от num & 1 == 0:

not  eax                        ; the parameter was passed in EAX
and  eax, 1

Для сравнения: GCC (начиная с версии 4.4) и Clang (начиная с версии 3.2) делают то, что можно было бы ожидать, создавая идентичный объектный код. для обоих вариантов. Однако согласно Интерактивный компилятор Мэтта Годболта, ICC 13.0.1 также не оправдывает моих ожиданий.

Конечно, эти компиляторы не ошибочны. Это не ошибка. Существует множество технических причин (как правильно указано в других ответах), почему эти два фрагмента кода не идентичны. И, безусловно, здесь следует привести аргумент «преждевременная оптимизация - зло». Конечно, есть причина, по которой мне потребовались годы, чтобы заметить это, и даже тогда я случайно наткнулся на это.

Но, как сказал Дуг Т., вероятно, лучше всего определить в вашей библиотеке функцию IsEven, которая получит все эти исправьте мелкие детали, чтобы вам больше не приходилось думать о них, и сохраняйте читабельность кода. Если вы регулярно нацеливаетесь на MSVC, возможно, вы определите эту функцию, как я:

bool IsEven(int value)
{
    const bool result = (num & 1) == 0;
    assert(result == ((num % 2) == 0));
    return result;   
}
person Cody Gray    schedule 10.08.2014
comment
Интересно, как эти версии компилятора работают на (x << y) | (x >> (32-y)) vs (x << y) | (x >> (31-y) >> 1)? IMHO, учитывая, что первый работал в 99% компиляторов C до 2009 года при использовании непедантичных настроек, стандарт должен был быть изменен, чтобы на n-битной машине x>>n всегда должен был либо x, либо x>>(n-1)>>1 (выбран произвольно) или ловушка способом, определяемым реализацией. Я бы считал, что первый код во всех отношениях превосходит второй, если бы не переосмысление того, как компиляторы должны вести себя в тех случаях, когда Стандарт не налагает никаких требований. - person supercat; 13.08.2015
comment
К счастью, в текущем MSVC больше нет этой ошибки пропущенной оптимизации. Godbolt восходит только к VS2015 (CL19.0), где это исправлено. Можно подумать, что они потрудились бы использовать специальный случай %2 целых чисел со знаком, когда результат проверяется только на ненулевое значение. x % 2 == 1 сложно, или как return x % 2 должен возвращать -1, 0 или 1 в зависимости от знака и младших битов для дополнения до 2. Но x % 2 == 0 в точности эквивалентен (x&1) == 0 при нацеливании на систему с дополнением до 2, такую ​​как x86. - person Peter Cordes; 03.07.2019
comment
И, кстати, для соглашения о вызове register-arg, такого как Windows fastcall, лучше всего было бы lea eax, [ecx + 1] перевернуть младший бит при копировании, а затем and eax,1 или and al,1 для размера кода, если вы возвращаете узкий bool. Но ни один из gcc / clang / MSVC / ICC этого не замечает. gcc.godbolt.org/z/ubvsfx Хотя clang действительно выбирает test dil,1 / sete al для автономного функций, но не при встраивании в main. - person Peter Cordes; 03.07.2019

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

Во всяком случае, я считаю, что 2) намного быстрее, так как не требует деления.

person Thomas Bonini    schedule 22.12.2009
comment
Вы можете протестировать его, но (1) также не требует деления. Любой компилятор, который вычисляет его таким образом, достаточно примитивен, поэтому микрооптимизации - далеко не самая большая проблема. - person David Thornley; 23.12.2009
comment
Если вы новичок в программировании и не знаете, что делает оператор по модулю, то, вероятно, вы все еще находитесь в своем первом классе программирования. - person Todd Gamblin; 23.12.2009

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

Компилятор может заменить версию по модулю эффективной версией. Но это звучит как предлог для предпочтения модулю.

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

При выводе значения, стоящего за утверждениями, может быть даже проще прочитать двоичную версию:

if( ( num & 1 ) == 0 ) { /* even */ }
if( ( 00010111b & 1 ) == 0 ) { /* even */ }
if( ( 00010110b & 1 ) == 0 ) { /* odd */ }

(Я использовал суффикс «b» только для пояснения, это не C / C ++)

С версией по модулю вы должны дважды проверить, как операция определена в ее деталях (например, проверьте документацию, чтобы убедиться, что 0 % 2 - это то, что вы ожидаете).

Бинарный AND проще и в нем нет двусмысленностей!

Только приоритет оператора может быть ловушкой с бинарными операторами. Но это не должно быть поводом избегать их (когда-нибудь они все равно понадобятся даже новым программистам).

person Frunsi    schedule 22.12.2009
comment
Пара точек: 0% 2 хорошо определено. Если вы знаете, что такое деление, ваш учитель должен был объяснять модули одновременно. Можно с уверенностью предположить, что разработчики знают, что это такое, поскольку мы ожидаем минимального уровня математических навыков. Для отрицательных нечетных чисел младший бит не может быть установлен в 1. - person Martin York; 23.12.2009
comment
@Martin: 0% 2 хорошо определен. Я не об этом. Модуло и деление не будут объяснены одновременно в школе. - person Frunsi; 23.12.2009
comment
Чтобы перевернуть вашу точку зрения, читатель, который плохо знаком с программированием, может не знать, что в представлении двух дополнительных чисел младший бит равен 0 для четных чисел. Он может даже не знать побитового оператора И! По крайней мере, решение по модулю имеет свойство отражать математическое определение четности. - person Adam; 23.12.2009
comment
Интересно, что двоичный литерал появился в C ++ 14: 0b00010111. - person L. F.; 13.09.2019

На этом этапе я, возможно, просто добавляю шума, но с точки зрения удобочитаемости вариант по модулю имеет больше смысла. Если ваш код не читается, он практически бесполезен.

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

person A. Walton    schedule 22.12.2009