web-dev-qa-db-fra.com

Trouver un maximum de trois nombres en C sans utiliser l'instruction conditionnelle et l'opérateur ternaire

Je dois trouver un maximum de trois numéros fournis par l'utilisateur, mais avec certaines restrictions. Il n'est pas autorisé à utiliser une instruction conditionnelle. J'ai essayé d'utiliser l'opérateur ternaire comme ci-dessous.

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

Mais encore une fois, son utilisation est limitée à l'opérateur ternaire. Maintenant, je ne sais pas comment faire cela?

30
Microsoft Developer

Profiter du court-circuit dans les expressions booléennes:

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;
}

Explication:

Dans une opération booléenne AND telle que x && y, y est évalué si et seulement si x est vrai. Si x est faux, alors y n'est pas évalué, car l'expression entière serait fausse, ce qui peut être déduit sans même évaluer y. C'est ce qu'on appelle un court-circuit lorsque la valeur d'une expression booléenne peut être déduite sans évaluer tous les opérandes qu'elle contient.

Appliquez ce principe au code ci-dessus. Initialement, m est a. Maintenant si (m < b) est vrai, cela signifie que b est supérieur à m (qui est en fait a), donc la deuxième sous-expression (m = b) est évalué et m est défini sur b. Si, toutefois (m < b) est faux, alors la deuxième sous-expression ne sera pas évaluée et m restera a (qui est supérieure à b). De la même manière, la deuxième expression est évaluée (sur la ligne suivante).

En bref, vous pouvez lire l'expression (m < x) && (m = x) comme suit: définissez m sur x si et seulement si m est inférieur à x ie (m < x) est vrai. J'espère que cela vous aidera à comprendre le code.

Code de test:

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;
}

Sortie:

3
3
3

Notez que l'implémentation de max donne des avertissements car les expressions évaluées ne sont pas utilisées:

prog.c: 6: avertissement: la valeur calculée n'est pas utilisée
prog.c: 7: avertissement: la valeur calculée n'est pas utilisée

Pour éviter ces avertissements (inoffensifs), vous pouvez implémenter max comme:

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;
}

L'astuce est que maintenant nous sommes transtypage des expressions booléennes en void, ce qui provoque la suppression des avertissements :

69
Nawaz

En supposant que vous traitez avec des entiers, que diriez-vous:

#define max(x,y) (x ^ ((x ^ y) & -(x < y)))
int max3(int x, int y, int z) {
    return max(max(x,y),z);
}
19
Foo Bah

Juste pour ajouter une autre alternative pour éviter l'exécution conditionnelle (qui n'est pas celle que j'utiliserais, mais qui semblait manquer dans l'ensemble de solutions):

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

L'approche utilise (comme la plupart des autres), le fait que le résultat d'une expression booléenne lorsqu'elle est convertie en int donne 0 ou 1. La version simplifiée pour deux valeurs serait:

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

Si l'expression a<b Est correcte, nous renvoyons b, soigneusement stocké dans le premier index du tableau de recherche. Si l'expression renvoie false, nous renvoyons a qui est stocké en tant qu'élément 0 Du tableau de recherche. En utilisant cela comme un bloc de construction, vous pouvez dire:

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

Ce qui peut être trivialement transformé en code ci-dessus en évitant le deuxième appel au max intérieur en utilisant le résultat déjà stocké dans lookup[0] Et en insérant l'appel d'origine à max(int,int).


(Cette partie est juste une autre preuve que vous devez mesurer avant de sauter dans les conclusions, voir l'édition à la fin)

Quant à ce que j'utiliserais réellement ... eh bien, probablement celui de @Foo Baa ici modifié pour utiliser une fonction en ligne plutôt qu'une macro. L'option suivante serait celle-ci ou celle de @MSN ici .

Le dénominateur commun de ces trois solutions non présentes dans la réponse acceptée est qu'elles évitent non seulement la construction syntaxique de if ou l'opérateur ternaire ?:, Mais qu'elles évitent branchement tout à fait, et cela peut avoir un impact sur les performances. Le prédicteur de branche dans le CPU ne peut pas manquer s'il n'y a pas de branche.


Lors de l'examen des performances, mesurez d'abord, puis réfléchissez

J'ai en fait implémenté quelques-unes des différentes options pour un max à 2 voies et analysé le code généré par le compilateur. Les trois solutions suivantes génèrent le même code d'assemblage:

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;
}

Ce qui n'est pas surprenant, car les trois représentent exactement la même opération. L'information intéressante est que le code généré ne contient aucune branche. L'implémentation est simple avec l'instruction cmovge (test effectué avec g ++ dans une plateforme 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

L'astuce réside dans l'instruction de déplacement conditionnel, qui évite toute branche potentielle.

Aucune des autres solutions n'a de branche, mais toutes se traduisent par plus d'instructions de processeur que tout cela, ce qui nous rassure en fin de compte que nous devons toujours écrire du code simple et laisser le compilateur l'optimiser pour nous.

MISE À JOUR: En regardant cela 4 ans plus tard, je vois qu'il échoue mal si deux ou plusieurs des valeurs se trouvent être égales. Remplacement de > par >= modifie le comportement, mais ne résout pas le problème. Il peut encore être récupérable, donc je ne le supprimerai pas encore, mais ne l'utilisez pas dans le code de production.


Ok, voici le mien:

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

Notez que l'utilisation de & plutôt que && évite tout code conditionnel; il repose sur le fait que > renvoie toujours 0 ou 1. (Le code généré pour a > b peut impliquer des sauts conditionnels, mais ils ne sont pas visibles depuis C.)

6
Keith Thompson
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));
}
4
MSN

Les opérateurs à valeur booléenne (y compris <, &&, etc.) se traduisent généralement en opérations conditionnelles au niveau du code machine, donc ne remplissez pas l'esprit du défi. Voici une solution que tout compilateur raisonnable ne traduirait qu'en instructions arithmétiques sans sauts conditionnels (en supposant que long a plus de bits qu'int et que long est de 64 bits). L'idée est que "m" capture et reproduit le bit de signe de b - a, donc m est soit tous les 1 bits (si a> b), soit tous les bits zéro (si a <= b). Notez que long est utilisé pour éviter le débordement. Si, pour une raison quelconque, vous savez que b - a ne déborde pas/ne déborde pas, l'utilisation de long n'est pas nécessaire.

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;
}
3
gsk

Pas de conditions. Seulement un casting à uint. Solution parfaite.

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;
}
2
Lee Louviere

Vous pouvez utiliser ce code pour trouver le plus grand sur deux:

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

puis utilisez-le à nouveau pour trouver le troisième numéro:

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

Voyez que cela fonctionne pour les nombres positifs, vous pouvez le changer pour qu'il fonctionne également pour les négatifs.

1
user5243048

Non instructions conditionnelles , juste des boucles et des affectations. Et les réponses des autres sont complètement différentes :)

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;
0
mok
#include "stdafx.h"
#include <iostream>
int main()
{       
        int x,y,z;
        scanf("%d %d %d", &x,&y, &z);
        int max = ((x+y) + abs(x-y)) /2;
        max = ((max+z) + abs(max-z)) /2;
        printf("%d ", max);
        return 0;
}            
0
Shqear