MIN y MAX en C


¿Dónde se definen MIN y MAX en C, si es que se definen?

¿Cuál es la mejor manera de implementarlos, de la manera más genérica y segura posible? (Extensiones de compilador / builtins para compiladores convencionales preferidos.)

Author: a3f, 2010-08-09

13 answers

¿Dónde se definen MIN y MAX en C, si es que se definen?

No lo son.{[12]]}

Cuál es la mejor manera de implementarlos, de la manera más genérica y segura posible (se prefieren las extensiones de compilador/builtins para compiladores convencionales).

Como funciones. No usaría macros como #define MIN(X, Y) (((X) < (Y)) ? (X) : (Y)), especialmente si planea implementar su código. Ya sea escribir su propia, utilizar algo como estándar fmax o fmin, o arreglar la macro usando typeof de GCC (también obtienes bonificación de seguridad de tipo):

 #define max(a,b) \
   ({ __typeof__ (a) _a = (a); \
       __typeof__ (b) _b = (b); \
     _a > _b ? _a : _b; })

Todo el mundo dice "oh, sé sobre la doble evaluación, no hay problema" y unos meses más adelante, estarás depurando los problemas más tontos durante horas y horas.

Tenga en cuenta el uso de __typeof__ en lugar de typeof:

Si está escribiendo un archivo de encabezado que debe funcionar cuando se incluye en ISO C programas, escribir __typeof__ en lugar de typeof.

 298
Author: David Titarenco,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-11-02 18:07:04

También se proporciona en las versiones GNU libc (Linux) y FreeBSD de sys/param.h, y tiene la definición proporcionada por dreamlax.


En 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.

En 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))

Los repositorios de código fuente están aquí:

 74
Author: Mikel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-08-15 02:56:13

Hay un std::min y std::max en C++, pero AFAIK, no hay equivalente en la biblioteca estándar de C. Puedes definirlos tú mismo con macros como

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

Pero esto causa problemas si escribes algo como MAX(++a, ++b).

 63
Author: dan04,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-08-09 04:56:57

Evite las extensiones de compilador no estándar e impleméntelo como una macro completamente segura de tipos en el estándar C puro (ISO 9899:2011).

Solución

#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))

Uso

MAX(int, 2, 3)

Explicación

La macro MAX crea otra macro basada en el parámetro type. Esta macro de control, si se implementa para el tipo dado, se utiliza para comprobar que ambos parámetros son del tipo correcto. Si el type no es compatible, habrá un error del compilador.

Si x o y no son del tipo correcto, habrá un error de compilador en las macros ENSURE_. Se pueden agregar más macros si se admiten más tipos. He asumido que solo se utilizarán tipos aritméticos (enteros, flotadores, punteros, etc.) y no estructuras o matrices, etc.

Si todos los tipos son correctos, se llamará a la macro GENERIC_MAX. Se necesitan paréntesis adicionales alrededor de cada parámetro macro, como la precaución estándar habitual al escribir C macro.

Luego están los problemas habituales con las promociones de tipo implícito en C. El operador ?: equilibra el 2do y 3ro operando entre sí. Por ejemplo, el resultado de GENERIC_MAX(my_char1, my_char2) sería un int. Para evitar que la macro haga tales promociones de tipo potencialmente peligrosas, se utilizó un molde de tipo final para el tipo previsto.

Justificación

Queremos que ambos parámetros de la macro sean del mismo tipo. Si uno de ellos es de un tipo diferente, la macro es ya no escriba seguro, porque un operador como ?: dará promociones de tipo implícitas. Y debido a que lo hace, también siempre necesitamos volver a lanzar el resultado final al tipo deseado como se explicó anteriormente.

Una macro con un solo parámetro podría haberse escrito de una manera mucho más simple. Pero con 2 o más parámetros, es necesario incluir un parámetro de tipo adicional. Porque algo como esto es desafortunadamente imposible:

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

El problema es que si la macro anterior es llamado como MAX(1, 2) con dos int, todavía intentará macro-expandir todos los escenarios posibles de la lista de asociaciones _Generic. Así que la macro ENSURE_float también se expandirá, aunque no sea relevante para int. Y dado que esa macro intencionalmente solo contiene el tipo float, el código no se compilará.

Para resolver esto, creé el nombre de la macro durante la fase de preprocesador en su lugar, con el operador##, para que ninguna macro se convierta accidentalmente expandido.

Ejemplos

#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,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-06-18 14:29:59

No creo que sean macros estandarizadas. Ya existen funciones estandarizadas para coma flotante, fmax y fmin (y fmaxf para flotadores, y fmaxl para dobles largos).

Puede implementarlos como macros siempre y cuando sea consciente de los problemas de efectos secundarios/doble evaluación.

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

En la mayoría de los casos, puede dejar que el compilador determine lo que está tratando de hacer y optimizarlo lo mejor que pueda. Si bien esto causa problemas cuando se usa como MAX(i++, j++), dudo siempre hay mucha necesidad de comprobar el máximo de valores incrementados de una sola vez. Incrementar primero, luego comprobar.

 16
Author: dreamlax,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-08-09 04:58:50

Esta es una respuesta tardía, debido a un desarrollo bastante reciente. Dado que el OP aceptó la respuesta que se basa en una extensión GCC (y clang) no portátil typeof-o __typeof__ para ISO C 'limpia' - hay una mejor solución disponible a partir de gcc - 4.9.

#define max(x,y) ( \
    { __auto_type __x = (x); __auto_type __y = (y); \
      __x > __y ? __x : __y; })

El beneficio obvio de esta extensión es que cada argumento macro solo se expande una vez, a diferencia de la solución __typeof__.

__auto_type es una forma limitada de C++11 auto. No puede (o no debe?) ser utilizado en código C++, aunque no hay una buena razón para no utilizar las capacidades superiores de inferencia de tipos de auto cuando se utiliza C++11.

Dicho esto, yo asumo que no hay problemas usando esta sintaxis cuando la macro se incluye en un ámbito extern "C" { ... }; por ejemplo, desde un encabezado C. AFAIK, esta extensión no ha encontrado su camino info clang

 14
Author: Brett Hale,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-20 00:18:05

Escribí esta versión que funciona para MSVC, GCC, C y 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,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-11-09 21:39:19

Si necesita min/max para evitar una rama costosa, no debe usar el operador ternario, ya que compilará hasta un salto. El siguiente enlace describe un método útil para implementar una función min/max sin ramificación.

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

 8
Author: cib,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-09-19 18:13:05

Sé que el tipo dijo "C"... Pero si tienes la oportunidad, usa una plantilla de C++:

template<class T> T min(T a, T b) { return a < b ? a : b; }

Escriba safe, y no hay problemas con el ++ mencionado en otros comentarios.

 5
Author: Bas Kuenen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-03-14 11:23:36

Vale la pena señalar Creo que si se define min y max con el terciario como

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

Entonces para obtener el mismo resultado para el caso especial de fmin(-0.0,0.0) y fmax(-0.0,0.0) necesita intercambiar los argumentos

fmax(a,b) = MAX(a,b)
fmin(a,b) = MIN(b,a)
 3
Author: Z boson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-06-18 12:42:57

Parece que Windef.h (a la #include <windows.h>) tiene max y min (minúsculas) macros, que también sufren de la dificultad de "doble evaluación", pero están ahí para aquellos que no quieren volver a rodar su propia:)

 2
Author: rogerdpack,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-05 03:51:22

El máximo de dos enteros a y b es (int)(0.5((a+b)+abs(a-b))). Esto también puede funcionar con (double) y fabs(a-b) para dobles (similar para flotadores)

 0
Author: NRZ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-21 10:14:35

La forma más sencilla es definirla como una función global en un archivo .h, y llamarla cuando quieras, si tu programa es modular con muchos archivos. Si no, double MIN(a,b){return (a<b?a:b)} es la forma más sencilla.

 -3
Author: srezat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-12 21:29:47