Найти максимум три числа в C без использования условного оператора и тернарного оператора - PullRequest
30 голосов
/ 16 августа 2011

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

max=(a>b?a:b)>c?(a>b?a:b):c

Но опять же его использование ограничено троичным оператором. Теперь я не понимаю, как это сделать?

Ответы [ 13 ]

69 голосов
/ 16 августа 2011

Использование короткого замыкания в логических выражениях:

int max(int a, int b, int c)
{
     int m = a;
     (m < b) && (m = b); //these are not conditional statements.
     (m < c) && (m = c); //these are just boolean expressions.
     return m;
}

Объяснение:

В логической операции AND, такой как x && y, y оценивается тогда и только тогда, когда x - истина. Если x ложно, то y не оценивается, поскольку все выражение будет ложным, что может быть выведено даже без оценки y. Это называется коротким замыканием, когда значение логического выражения может быть выведено без оценки всех операндов в нем.

Примените этот принцип к приведенному выше коду. Первоначально m является a. Теперь, если (m < b) истинно, то это означает, что b больше m (что на самом деле a), поэтому второе подвыражение (m = b) оценивается и m устанавливается на b. Однако, если (m < b) равно false, второе подвыражение не будет оцениваться, а m останется a (что больше b). Аналогичным образом вычисляется второе выражение (на следующей строке).

Короче говоря, вы можете прочитать выражение (m < x) && (m = x) следующим образом: установите m в x тогда и только тогда, когда m меньше x, т.е. (m < x) истинно , Надеюсь, это поможет вам понять код.

Тестовый код:

int main() {
        printf("%d\n", max(1,2,3));
        printf("%d\n", max(2,3,1));
        printf("%d\n", max(3,1,2));
        return 0;
}

Выход:

3
3
3

Обратите внимание, что реализация max выдает предупреждения, поскольку вычисленные выражения не используются:

prog.c: 6: предупреждение: вычисленное значение не используется
prog.c: 7: предупреждение: вычисленное значение не используется

Чтобы избежать этих (безвредных) предупреждений, вы можете реализовать max как:

int max(int a, int b, int c)
{
     int m = a;
     (void)((m < b) && (m = b)); //these are not conditional statements.
     (void)((m < c) && (m = c)); //these are just boolean expressions.
     return m;
}

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

19 голосов
/ 16 августа 2011

Предполагая, что вы имеете дело с целыми числами, как насчет:

#define max(x,y) (x ^ ((x ^ y) & -(x < y)))
int max3(int x, int y, int z) {
    return max(max(x,y),z);
}
15 голосов
/ 25 августа 2011

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

int max( int a, int b, int c ) {
   int l1[] = { a, b };
   int l2[] = { l1[ a<b ], c };
   return l2[ l2[0] < c ];
}

Подход использует (как и большинство других) тот факт, что результат логического выражения при преобразовании в int дает либо 0, либо 1. Упрощенная версия для двух значений будет иметь вид:

int max( int a, int b ) {
   int lookup[] { a, b };
   return lookup[ a < b ];
}

Если выражение a<b верное, мы возвращаем b, тщательно сохраненные в первом индексе поискового массива. Если выражение возвращает false, то мы возвращаем a, который хранится как элемент 0 массива поиска. Используя это как строительный блок, вы можете сказать:

int max( int a, int b, int c ) {
   int lookup[ max(a,b), c ];
   return lookup[ max(a,b) < c ];
}

Что можно тривиально преобразовать в приведенный выше код, избегая второго вызова внутреннего max, используя результат, уже сохраненный в lookup[0], и вставляя исходный вызов в max(int,int).


(Эта часть является еще одним доказательством того, что вы должны измерить, прежде чем делать выводы, см. Редактирование в конце)

Что бы я на самом деле использовал ... ну, наверное, тот, который @Foo Baa здесь , модифицированный для использования встроенной функции, а не макроса. Следующим вариантом будет либо этот, либо один из @MSN здесь .

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


При рассмотрении вопроса о производительности сначала подумайте, а затем подумайте

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

int max( int a, int b ) { if ( a < b ) return b; else return a; }
int max( int a, int b ) { return (a < b? b : a ); }
int max( int a, int b ) {
   (void)((a < b) && (a = b));
   return a;
}

Что неудивительно, поскольку все три представляют одну и ту же операцию. Интересная часть информации состоит в том, что сгенерированный код не содержит никакой ветви. Реализация проста с инструкцией cmovge (тест, проведенный с g ++ на платформе Intel x64):

movl    %edi, %eax       # move a into the return value
cmpl    %edi, %esi       # compare a and b
cmovge  %esi, %eax       # if (b>a), move b into the return value
ret

Хитрость заключается в инструкции условного перемещения, которая позволяет избежать любой потенциальной ветви.

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

6 голосов
/ 19 августа 2011

ОБНОВЛЕНИЕ: Глядя на это 4 года спустя, я вижу, что он плохо работает, если два или более значений оказываются равными.Замена > на >= меняет поведение, но не решает проблему.Это все еще может быть восстановлено, поэтому я пока не буду удалять его, но не используйте это в рабочем коде.


Хорошо, вот мое:

int max3(int a, int b, int c)
{
    return a * (a > b & a > c) +
           b * (b > a & b > c) +
           c * (c > a & c > b);
}

Обратите внимание, чтоиспользование & вместо && позволяет избежать условного кода;он основан на том факте, что > всегда возвращает 0 или 1. (Код, сгенерированный для a > b, может включать условные переходы, но они не видны из C).

4 голосов
/ 16 августа 2011
int fast_int_max(int a, int b)
{
    int select= -(a < b);
    unsigned int b_mask= select, a_mask= ~b_mask;

    return (a&a_mask)|(b&b_mask);
}

int fast_int_max3(int a, int b, int c)
{
    return fast_int_max(a, fast_int_max(b, c));
}
3 голосов
/ 01 марта 2012

Булевозначные операторы (включая <, && и т. Д.) Обычно переводят в условные операции на уровне машинного кода, поэтому не выполняют дух задачи.Вот решение, которое любой разумный компилятор мог бы преобразовать только в арифметические инструкции без условных переходов (при условии, что long имеет больше битов, чем int, и что long составляет 64 бита).Идея состоит в том, что «m» захватывает и реплицирует знаковый бит b - a, поэтому m - это либо все 1 бит (если a> b), либо все нулевые биты (если a <= b).Обратите внимание, что long используется, чтобы избежать переполнения.Если по какой-то причине вы знаете, что b - a не превышает / не превышает поток, тогда использование long не требуется. </p>

int max(int a, int b)
{
    long d = (long)b - (long)a;
    int m = (int)(d >> 63);
    return a & m | b & ~m;
}

int max(int a, int b, int c)
{
    long d;
    int m;
    d = (long)b - (long)a;
    m = (int)(d >> 63);
    a = a & m | b & ~m;
    d = (long)c - (long)a;
    m = (int)(d >> 63);
    return a & m | c & ~m;
}
2 голосов
/ 12 ноября 2011

Без условий.Только актерский состав.Идеальное решение.

int abs (a) { return (int)((unsigned int)a); }
int max (a, b) { return (a + b + abs(a - b)) / 2; }
int min (a, b) { return (a + b - abs(a - b)) / 2; }


void sort (int & a, int & b, int & c)
{
   int max = max(max(a,b), c);
   int min = min(min(a,b), c);
   int middle = middle = a + b + c - max - min;
   a = max;
   b = middle;
   c = min;
}
1 голос
/ 19 августа 2015

Вы можете использовать этот код, чтобы найти наибольшее из двух:

max{a,b} = abs(a-b)/2 + (a+b)/2

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

max{a,b,c} = max(a,max(b,c))

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

0 голосов
/ 12 декабря 2014

Попробуйте это.

#include "stdio.h"
main() {
    int a,b,c,rmvivek,arni,csc; 
    printf("enter the three numbers");
    scanf("%d%d%d",&a,&b,&c);
    printf("the biggest value is %d",(a>b&&a>c?a:b>c?b:c));
}
0 голосов
/ 22 марта 2014

Нет условные операторы , только циклы и присваивания. И совершенно разные формы ответов других:)

while (a > b)
{
    while (a > c)
    {
        tmp = a;
        goto finish;
    }
    tmp = c;
    goto finish;
}
while (b > c)
{
    tmp = b;
    goto finish;
}
tmp = c;
finish: max = tmp;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...