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

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

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

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

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

Как вы думаете?

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

Ответ 1

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

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

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

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

Ответ 2

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

Я считаю, что второй вариант будет гораздо более изящным и значимым

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

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

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

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

Ответ 3

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

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

Ответ 4

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

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

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

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

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

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

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

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

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

Ответ 5

Было упомянуто несколько раз, что любой современный компилятор создавал бы одну и ту же сборку для обеих опций. Это напомнило мне демонстрационную страницу 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

Ответ 6

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

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

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

if( (x&1) == 0 )

не

if( x&1 == 0 )

Последний ANDs x с 1 == 0, т.е. он ANDs x с 0, что дает 0, который всегда всегда считается ложным.

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

Ответ 7

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

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

Ответ 8

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

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

Ответ 9

Я потратил годы, настаивая на том, что любой разумный компилятор, достойный пространства, которое он потребляет на диске, оптимизирует 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) делают то, что можно было бы ожидать, генерируя идентичный объектный код для обоих вариантов. Однако, согласно интерактивный компилятор Matt Godbolt, ICC 13.0.1 также не оправдывает моих ожиданий.

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

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

bool IsEven(int value)
{
    const bool result = (num & 1) == 0;
    assert(result == ((num % 2) == 0));
    return result;   
}

Ответ 10

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

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

Ответ 11

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

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

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

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

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

(Я использовал суффикс "b" только для уточнения, его не C/С++)

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

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

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

Ответ 12

В этот момент я могу просто добавить шум, но, насколько читаемость, опция modulo имеет больше смысла. Если ваш код не читается, он практически бесполезен.

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