Mín e máx em C

Onde estão MIN e MAX definidos em C, Se é que estão?

Qual é a melhor maneira de implementar estes, o mais genericamente e tipo de segurança possível? (Compiler extensions/builtins for mainstream compilers preferred.)

Author: a3f, 2010-08-09

13 answers

Onde estão MIN e MAX definidos em C, Se é que estão?

Não são.

Qual é a melhor maneira de implementar estes, o mais genericamente e digitar seguro possível (extensões de compilador/builtins para compiladores mainstream preferidos).

Como funções. Eu não usaria macros como #define MIN(X, Y) (((X) < (Y)) ? (X) : (Y)), especialmente se você planeja implantar o seu código. Ou escreves o teu, usas algo como padrão.fmax ou fmin, ou corrigir a macro usando o tipo de letra do GCC (Você também recebe bónus de segurança do tipo de letra):

 #define max(a,b) \
   ({ __typeof__ (a) _a = (a); \
       __typeof__ (b) _b = (b); \
     _a > _b ? _a : _b; })
Todos dizem: "sei da dupla avaliação, não há problema" e, daqui a alguns meses, vais acabar por debugar os problemas mais idiotas durante horas a fio.

Note a utilização de __typeof__ em vez de typeof:

Se estiver a escrever um ficheiro de cabeçalho que deve funcionar quando incluído na norma ISO C programas, escrever __typeof__ em vez de typeof.

 298
Author: David Titarenco, 2014-11-02 18:07:04

Também é fornecido nas versões GNU libc (Linux) e FreeBSD do sys/param.h, E tem a definição fornecida por dreamlax.


No Debian:

$ uname -sr
Linux 2.6.11

$ cat /etc/debian_version
5.0.2

$ egrep 'MIN\(|MAX\(' /usr/include/sys/param.h
#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))

$ head -n 2 /usr/include/sys/param.h | grep GNU
This file is part of the GNU C Library.

No FreeBSD:

$ uname -sr
FreeBSD 5.5-STABLE

$ egrep 'MIN\(|MAX\(' /usr/include/sys/param.h
#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))

Os repositórios de código estão aqui:

 74
Author: Mikel, 2010-08-15 02:56:13

Há um std::min e std::max Em C++, mas AFAIK, não há equivalente na Biblioteca Padrão de C. Você pode defini-los com macros como

#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define MIN(x, y) (((x) < (y)) ? (x) : (y))

Mas isso causa problemas se você escrever algo como MAX(++a, ++b).

 63
Author: dan04, 2010-08-09 04:56:57

Evitar extensões de compilador não-padrão e implementá-lo como uma macro completamente segura em puro padrão C (ISO 9899:2011).

Solução

#define GENERIC_MAX(x, y) ((x) > (y) ? (x) : (y))

#define ENSURE_int(i)   _Generic((i), int:   (i))
#define ENSURE_float(f) _Generic((f), float: (f))


#define MAX(type, x, y) \
  (type)GENERIC_MAX(ENSURE_##type(x), ENSURE_##type(y))

Utilização

MAX(int, 2, 3)

Anotações

A macro MAX cria outra macro baseada no parâmetro type. Esta macro de controle, se implementada para o tipo dado, é usada para verificar se ambos os parâmetros são do tipo correto. Se o type não for suportado, existe será um erro de compilador.

Se x ou y não for do tipo correcto, haverá um erro de compilador nas macros ENSURE_. Mais macros podem ser adicionados se mais tipos forem suportados. Eu assumi que somente tipos aritméticos (inteiros, flutuadores, ponteiros, etc) serão usados e não estruturas ou arrays, etc.

Se todos os tipos estiverem correctos, a macro GENERIC_MAX será chamada. Parêntesis Extra são necessários em torno de cada parâmetro de macro, como a precaução padrão habitual ao escrever C macro.

Depois há os problemas habituais com promoções implícitas de tipo C. o operador ?:equilibra o segundo e o terceiro operando um contra o outro. Por exemplo, o resultado de GENERIC_MAX(my_char1, my_char2) seria um int. Para evitar que a macro fizesse tais promoções de tipo potencialmente perigosas, foi utilizado um molde final para o tipo pretendido.

Fundamentação

Queremos que ambos os parâmetros da macro sejam do mesmo tipo. Se um deles é de um tipo diferente, a macro é não mais Digite seguro, porque um operador como ?: irá produzir promoções implícitas de tipo. E porque isso acontece, nós também precisamos sempre lançar o resultado final de volta para o tipo pretendido, como explicado acima.

Uma macro com apenas um parâmetro poderia ter sido escrita de uma forma muito mais simples. Mas com 2 ou mais parâmetros, há uma necessidade de incluir um parâmetro de tipo extra. Porque algo assim é infelizmente impossível.

// this won't work
#define MAX(x, y)                                  \
  _Generic((x),                                    \
           int: GENERIC_MAX(x, ENSURE_int(y))      \
           float: GENERIC_MAX(x, ENSURE_float(y))  \
          )

O problema é que se a macro acima é chamado como MAX(1, 2) com dois int, ele ainda vai tentar macro-expandir todos os cenários possíveis da lista de associação _Generic. Assim, a macro ENSURE_float também será expandida, mesmo que não seja relevante para int. E como essa macro intencionalmente só contém o tipo float, o código não será compilado.

Para resolver isto, criei o nome da macro durante a fase de pré-processador, com o operador##, para que nenhuma macro fique acidentalmente expandir.

Exemplos

#include <stdio.h>

#define GENERIC_MAX(x, y) ((x) > (y) ? (x) : (y))

#define ENSURE_int(i)   _Generic((i), int:   (i))
#define ENSURE_float(f) _Generic((f), float: (f))


#define MAX(type, x, y) \
  (type)GENERIC_MAX(ENSURE_##type(x), ENSURE_##type(y))

int main (void)
{
  int    ia = 1,    ib = 2;
  float  fa = 3.0f, fb = 4.0f;
  double da = 5.0,  db = 6.0;

  printf("%d\n", MAX(int,   ia, ib)); // ok
  printf("%f\n", MAX(float, fa, fb)); // ok

//printf("%d\n", MAX(int,   ia, fa));  compiler error, one of the types is wrong
//printf("%f\n", MAX(float, fa, ib));  compiler error, one of the types is wrong
//printf("%f\n", MAX(double, fa, fb)); compiler error, the specified type is wrong
//printf("%f\n", MAX(float, da, db));  compiler error, one of the types is wrong

//printf("%d\n", MAX(unsigned int, ia, ib)); // wont get away with this either
//printf("%d\n", MAX(int32_t, ia, ib)); // wont get away with this either
  return 0;
}
 17
Author: Lundin, 2015-06-18 14:29:59
Acho que não são macros normalizados. Já existem funções normalizadas para vírgula flutuante, fmax e fmin (e fmaxf para flutuadores e fmaxl para duplos longos).

Pode implementá-los como macros, desde que esteja ciente das questões dos efeitos secundários/dupla avaliação.

#define MAX(a,b) ((a) > (b) ? a : b)
#define MIN(a,b) ((a) < (b) ? a : b)
Na maioria dos casos, você pode deixar para o compilador determinar o que você está tentando fazer e otimizá-lo o melhor que pode. Embora isso cause problemas quando usado como MAX(i++, j++), Eu duvido que haja sempre muita necessidade em verificar o máximo de valores incrementados de uma só vez. Incrementem primeiro, depois verifiquem.
 16
Author: dreamlax, 2010-08-09 04:58:50
Esta é uma resposta tardia, devido a um desenvolvimento bastante recente. Uma vez que o OP aceitou a resposta que se baseia numa extensão GCC (e clang) Não Portátil typeof-ou __typeof__ para ISO "limpo" C - existe uma solução melhor disponível a partir de gcc - 4.9.
#define max(x,y) ( \
    { __auto_type __x = (x); __auto_type __y = (y); \
      __x > __y ? __x : __y; })

O benefício óbvio desta extensão é que cada argumento macro só é expandido uma vez, ao contrário da solução __typeof__.

__auto_type é uma forma limitada de C++11's auto. Não pode (ou não deve?) ser usado no código C++ , embora não haja uma boa razão para não usar as capacidades de inferência do tipo superior de auto ao usar o C++11.

Dito isto, eu suponha que não existem problemas usando esta sintaxe quando a macro está incluída num âmbito extern "C" { ... }; por exemplo, a partir de um cabeçalho C. AFAIK, esta extensão não encontrou o seu caminho info clang

 14
Author: Brett Hale, 2015-08-20 00:18:05

Eu escrevi esta versão que funciona para MSVC, GCC, C E C++.

#if defined(__cplusplus) && !defined(__GNUC__)
#   include <algorithm>
#   define MIN std::min
#   define MAX std::max
//#   define TMIN(T, a, b) std::min<T>(a, b)
//#   define TMAX(T, a, b) std::max<T>(a, b)
#else
#       define _CHOOSE2(binoper, lexpr, lvar, rexpr, rvar) \
                ({ \
                        decltype(lexpr) lvar = (lexpr); \
                        decltype(rexpr) rvar = (rexpr); \
                        lvar binoper rvar ? lvar : rvar; \
                })
#       define _CHOOSE_VAR2(prefix, unique) prefix##unique
#       define _CHOOSE_VAR(prefix, unique) _CHOOSE_VAR2(prefix, unique)
#       define _CHOOSE(binoper, lexpr, rexpr) \
                _CHOOSE2( \
                        binoper, \
                        lexpr, _CHOOSE_VAR(_left, __COUNTER__), \
                        rexpr, _CHOOSE_VAR(_right, __COUNTER__) \
                )
#       define MIN(a, b) _CHOOSE(<, a, b)
#       define MAX(a, b) _CHOOSE(>, a, b)
#endif
 11
Author: Matt Joiner, 2012-11-09 21:39:19

Se você precisa de min / max para evitar um ramo caro, você não deve usar o operador ternário, pois ele irá compilar até um salto. A ligação abaixo descreve um método útil para implementar uma função min/max sem ramificar.

Http://graphics.stanford.edu/~seander/bithacks.html#IntegerMinOrMax

 8
Author: cib, 2011-09-19 18:13:05
Eu sei que o tipo disse "C"... Mas se tiver oportunidade, use um modelo C++:
template<class T> T min(T a, T b) { return a < b ? a : b; }

Type safe, and no problems with the ++ mentioned in other comments.

 5
Author: Bas Kuenen, 2013-03-14 11:23:36

Vale a pena salientar que acho que se definirmos min e max com o terciário como

#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))

Então para obter o mesmo resultado para o caso especial de fmin(-0.0,0.0) e fmax(-0.0,0.0) você precisa trocar os argumentos

fmax(a,b) = MAX(a,b)
fmin(a,b) = MIN(b,a)
 3
Author: Z boson, 2015-06-18 12:42:57

Parece que {[[0]} (a la #include <windows.h>) tem max e min (minúsculas) macros, que também sofrem da dificuldade de "dupla avaliação", mas eles estão lá para aqueles que não querem voltar a rolar os seus próprios:)

 2
Author: rogerdpack, 2016-12-05 03:51:22

O máximo de dois inteiros a e b é (int)(0.5((a+b)+abs(a-b))). Isto também pode funcionar com (double) e fabs(a-b) para duplos (semelhantes para flutuadores)

 0
Author: NRZ, 2014-07-21 10:14:35

A maneira mais simples é defini-lo como uma função global num ficheiro .h, e chamá-lo sempre que quiser, se o seu programa for modular com muitos ficheiros. Se não, double MIN(a,b){return (a<b?a:b)} é a maneira mais simples.

 -3
Author: srezat, 2018-04-12 21:29:47